Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 declare(strict_types=1); 18 19 namespace core_reportbuilder\local\entities; 20 21 use context_course; 22 use context_helper; 23 use core_reportbuilder\local\filters\boolean_select; 24 use core_reportbuilder\local\filters\course_selector; 25 use core_reportbuilder\local\filters\date; 26 use core_reportbuilder\local\filters\select; 27 use core_reportbuilder\local\filters\text; 28 use core_reportbuilder\local\helpers\custom_fields; 29 use core_reportbuilder\local\helpers\format; 30 use core_reportbuilder\local\report\column; 31 use core_reportbuilder\local\report\filter; 32 use html_writer; 33 use lang_string; 34 use stdClass; 35 36 defined('MOODLE_INTERNAL') || die(); 37 38 global $CFG; 39 require_once($CFG->dirroot . '/course/lib.php'); 40 41 /** 42 * Course entity class implementation 43 * 44 * This entity defines all the course columns and filters to be used in any report. 45 * 46 * @package core_reportbuilder 47 * @copyright 2021 Sara Arjona <sara@moodle.com> based on Marina Glancy code. 48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 49 */ 50 class course extends base { 51 52 /** 53 * Database tables that this entity uses and their default aliases. 54 * 55 * @return array 56 */ 57 protected function get_default_table_aliases(): array { 58 return [ 59 'course' => 'c', 60 'context' => 'cctx', 61 'tag_instance' => 'cti', 62 'tag' => 'ct', 63 ]; 64 } 65 66 /** 67 * The default title for this entity in the list of columns/filters in the report builder. 68 * 69 * @return lang_string 70 */ 71 protected function get_default_entity_title(): lang_string { 72 return new lang_string('entitycourse', 'core_reportbuilder'); 73 } 74 75 /** 76 * Get custom fields helper 77 * 78 * @return custom_fields 79 */ 80 protected function get_custom_fields(): custom_fields { 81 $customfields = new custom_fields($this->get_table_alias('course') . '.id', $this->get_entity_name(), 82 'core_course', 'course'); 83 $customfields->add_joins($this->get_joins()); 84 return $customfields; 85 } 86 87 /** 88 * Initialise the entity, adding all course and custom course fields 89 * 90 * @return base 91 */ 92 public function initialise(): base { 93 $customfields = $this->get_custom_fields(); 94 95 $columns = array_merge($this->get_all_columns(), $customfields->get_columns()); 96 foreach ($columns as $column) { 97 $this->add_column($column); 98 } 99 100 $filters = array_merge($this->get_all_filters(), $customfields->get_filters()); 101 foreach ($filters as $filter) { 102 $this 103 ->add_condition($filter) 104 ->add_filter($filter); 105 } 106 107 return $this; 108 } 109 110 /** 111 * Return syntax for joining on the context table 112 * 113 * @return string 114 */ 115 public function get_context_join(): string { 116 $coursealias = $this->get_table_alias('course'); 117 $contextalias = $this->get_table_alias('context'); 118 119 return "LEFT JOIN {context} {$contextalias} 120 ON {$contextalias}.contextlevel = " . CONTEXT_COURSE . " 121 AND {$contextalias}.instanceid = {$coursealias}.id"; 122 } 123 124 /** 125 * Course fields. 126 * 127 * @return array 128 */ 129 protected function get_course_fields(): array { 130 return [ 131 'fullname' => new lang_string('fullnamecourse'), 132 'shortname' => new lang_string('shortnamecourse'), 133 'idnumber' => new lang_string('idnumbercourse'), 134 'summary' => new lang_string('coursesummary'), 135 'format' => new lang_string('format'), 136 'startdate' => new lang_string('startdate'), 137 'enddate' => new lang_string('enddate'), 138 'visible' => new lang_string('coursevisibility'), 139 'groupmode' => new lang_string('groupmode', 'group'), 140 'groupmodeforce' => new lang_string('groupmodeforce', 'group'), 141 'lang' => new lang_string('forcelanguage'), 142 'calendartype' => new lang_string('forcecalendartype', 'calendar'), 143 'theme' => new lang_string('forcetheme'), 144 'enablecompletion' => new lang_string('enablecompletion', 'completion'), 145 'downloadcontent' => new lang_string('downloadcoursecontent', 'course'), 146 ]; 147 } 148 149 /** 150 * Check if this field is sortable 151 * 152 * @param string $fieldname 153 * @return bool 154 */ 155 protected function is_sortable(string $fieldname): bool { 156 // Some columns can't be sorted, like longtext or images. 157 $nonsortable = [ 158 'summary', 159 ]; 160 161 return !in_array($fieldname, $nonsortable); 162 } 163 164 /** 165 * Return appropriate column type for given user field 166 * 167 * @param string $coursefield 168 * @return int 169 */ 170 protected function get_course_field_type(string $coursefield): int { 171 switch ($coursefield) { 172 case 'downloadcontent': 173 case 'enablecompletion': 174 case 'groupmodeforce': 175 case 'visible': 176 $fieldtype = column::TYPE_BOOLEAN; 177 break; 178 case 'startdate': 179 case 'enddate': 180 $fieldtype = column::TYPE_TIMESTAMP; 181 break; 182 case 'summary': 183 $fieldtype = column::TYPE_LONGTEXT; 184 break; 185 case 'groupmode': 186 $fieldtype = column::TYPE_INTEGER; 187 break; 188 case 'calendartype': 189 case 'idnumber': 190 case 'format': 191 case 'fullname': 192 case 'lang': 193 case 'shortname': 194 case 'theme': 195 default: 196 $fieldtype = column::TYPE_TEXT; 197 break; 198 } 199 200 return $fieldtype; 201 } 202 203 /** 204 * Return joins necessary for retrieving tags 205 * 206 * @return string[] 207 */ 208 public function get_tag_joins(): array { 209 return $this->get_tag_joins_for_entity('core', 'course', $this->get_table_alias('course') . '.id'); 210 } 211 212 /** 213 * Returns list of all available columns. 214 * 215 * These are all the columns available to use in any report that uses this entity. 216 * 217 * @return column[] 218 */ 219 protected function get_all_columns(): array { 220 global $DB; 221 222 $coursefields = $this->get_course_fields(); 223 $tablealias = $this->get_table_alias('course'); 224 $contexttablealias = $this->get_table_alias('context'); 225 226 // Columns course full name with link, course short name with link and course id with link. 227 $fields = [ 228 'coursefullnamewithlink' => 'fullname', 229 'courseshortnamewithlink' => 'shortname', 230 'courseidnumberewithlink' => 'idnumber', 231 ]; 232 foreach ($fields as $key => $field) { 233 $column = (new column( 234 $key, 235 new lang_string($key, 'core_reportbuilder'), 236 $this->get_entity_name() 237 )) 238 ->add_joins($this->get_joins()) 239 ->set_type(column::TYPE_TEXT) 240 ->add_fields("{$tablealias}.{$field} as $key, {$tablealias}.id") 241 ->set_is_sortable(true) 242 ->add_callback(static function(?string $value, stdClass $row): string { 243 if ($value === null) { 244 return ''; 245 } 246 247 context_helper::preload_from_record($row); 248 249 return html_writer::link(course_get_url($row->id), 250 format_string($value, true, ['context' => context_course::instance($row->id)])); 251 }); 252 253 // Join on the context table so that we can use it for formatting these columns later. 254 if ($key === 'coursefullnamewithlink') { 255 $column->add_join($this->get_context_join()) 256 ->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias)); 257 } 258 259 $columns[] = $column; 260 } 261 262 foreach ($coursefields as $coursefield => $coursefieldlang) { 263 $columntype = $this->get_course_field_type($coursefield); 264 265 $columnfieldsql = "{$tablealias}.{$coursefield}"; 266 if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') { 267 $columnfieldsql = $DB->sql_order_by_text($columnfieldsql, 1024); 268 } 269 270 $column = (new column( 271 $coursefield, 272 $coursefieldlang, 273 $this->get_entity_name() 274 )) 275 ->add_joins($this->get_joins()) 276 ->set_type($columntype) 277 ->add_field($columnfieldsql, $coursefield) 278 ->add_callback([$this, 'format'], $coursefield) 279 ->set_is_sortable($this->is_sortable($coursefield)); 280 281 // Join on the context table so that we can use it for formatting these columns later. 282 if ($coursefield === 'summary' || $coursefield === 'shortname' || $coursefield === 'fullname') { 283 $column->add_join($this->get_context_join()) 284 ->add_field("{$tablealias}.id", 'courseid') 285 ->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias)); 286 } 287 288 $columns[] = $column; 289 } 290 291 return $columns; 292 } 293 294 /** 295 * Returns list of all available filters 296 * 297 * @return array 298 */ 299 protected function get_all_filters(): array { 300 global $DB; 301 302 $filters = []; 303 $tablealias = $this->get_table_alias('course'); 304 305 $fields = $this->get_course_fields(); 306 foreach ($fields as $field => $name) { 307 $filterfieldsql = "{$tablealias}.{$field}"; 308 if ($this->get_course_field_type($field) === column::TYPE_LONGTEXT) { 309 $filterfieldsql = $DB->sql_cast_to_char($filterfieldsql); 310 } 311 312 $optionscallback = [static::class, 'get_options_for_' . $field]; 313 if (is_callable($optionscallback)) { 314 $filterclass = select::class; 315 } else if ($this->get_course_field_type($field) === column::TYPE_BOOLEAN) { 316 $filterclass = boolean_select::class; 317 } else if ($this->get_course_field_type($field) === column::TYPE_TIMESTAMP) { 318 $filterclass = date::class; 319 } else { 320 $filterclass = text::class; 321 } 322 323 $filter = (new filter( 324 $filterclass, 325 $field, 326 $name, 327 $this->get_entity_name(), 328 $filterfieldsql 329 )) 330 ->add_joins($this->get_joins()); 331 332 // Populate filter options by callback, if available. 333 if (is_callable($optionscallback)) { 334 $filter->set_options_callback($optionscallback); 335 } 336 337 $filters[] = $filter; 338 } 339 340 // We add our own custom course selector filter. 341 $filters[] = (new filter( 342 course_selector::class, 343 'courseselector', 344 new lang_string('courseselect', 'core_reportbuilder'), 345 $this->get_entity_name(), 346 "{$tablealias}.id" 347 )) 348 ->add_joins($this->get_joins()); 349 350 return $filters; 351 } 352 353 /** 354 * Gets list of options if the filter supports it 355 * 356 * @param string $fieldname 357 * @return null|array 358 */ 359 protected function get_options_for(string $fieldname): ?array { 360 static $cached = []; 361 if (!array_key_exists($fieldname, $cached)) { 362 $callable = [static::class, 'get_options_for_' . $fieldname]; 363 if (is_callable($callable)) { 364 $cached[$fieldname] = $callable(); 365 } else { 366 $cached[$fieldname] = null; 367 } 368 } 369 return $cached[$fieldname]; 370 } 371 372 /** 373 * List of options for the field groupmode. 374 * 375 * @return array 376 */ 377 public static function get_options_for_groupmode(): array { 378 return [ 379 NOGROUPS => get_string('groupsnone', 'group'), 380 SEPARATEGROUPS => get_string('groupsseparate', 'group'), 381 VISIBLEGROUPS => get_string('groupsvisible', 'group'), 382 ]; 383 } 384 385 /** 386 * List of options for the field format. 387 * 388 * @return array 389 */ 390 public static function get_options_for_format(): array { 391 global $CFG; 392 require_once($CFG->dirroot.'/course/lib.php'); 393 394 $options = []; 395 396 $courseformats = get_sorted_course_formats(true); 397 foreach ($courseformats as $courseformat) { 398 $options[$courseformat] = get_string('pluginname', "format_{$courseformat}"); 399 } 400 401 return $options; 402 } 403 404 /** 405 * List of options for the field theme. 406 * 407 * @return array 408 */ 409 public static function get_options_for_theme(): array { 410 $options = []; 411 412 $themeobjects = get_list_of_themes(); 413 foreach ($themeobjects as $key => $theme) { 414 if (empty($theme->hidefromselector)) { 415 $options[$key] = get_string('pluginname', "theme_{$theme->name}"); 416 } 417 } 418 419 return $options; 420 } 421 422 /** 423 * List of options for the field lang. 424 * 425 * @return array 426 */ 427 public static function get_options_for_lang(): array { 428 return get_string_manager()->get_list_of_translations(); 429 } 430 431 /** 432 * List of options for the field. 433 * 434 * @return array 435 */ 436 public static function get_options_for_calendartype(): array { 437 return \core_calendar\type_factory::get_list_of_calendar_types(); 438 } 439 440 /** 441 * Formats the course field for display. 442 * 443 * @param mixed $value Current field value. 444 * @param stdClass $row Complete row. 445 * @param string $fieldname Name of the field to format. 446 * @return string 447 */ 448 public function format($value, stdClass $row, string $fieldname): string { 449 if ($this->get_course_field_type($fieldname) === column::TYPE_TIMESTAMP) { 450 return format::userdate($value, $row); 451 } 452 453 $options = $this->get_options_for($fieldname); 454 if ($options !== null && array_key_exists($value, $options)) { 455 return $options[$value]; 456 } 457 458 if ($this->get_course_field_type($fieldname) === column::TYPE_BOOLEAN) { 459 return format::boolean_as_text($value); 460 } 461 462 if (in_array($fieldname, ['fullname', 'shortname'])) { 463 if (!$row->courseid) { 464 return ''; 465 } 466 context_helper::preload_from_record($row); 467 $context = context_course::instance($row->courseid); 468 return format_string($value, true, ['context' => $context->id, 'escape' => false]); 469 } 470 471 if (in_array($fieldname, ['summary'])) { 472 if (!$row->courseid) { 473 return ''; 474 } 475 context_helper::preload_from_record($row); 476 $context = context_course::instance($row->courseid); 477 $summary = file_rewrite_pluginfile_urls($row->summary, 'pluginfile.php', $context->id, 'course', 'summary', null); 478 return format_text($summary); 479 } 480 481 return s($value); 482 } 483 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body