See Release Notes
Long Term Support Release
Differences Between: [Versions 400 and 401] [Versions 401 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 machine-readable name for this entity that will be used in the internal names of the columns/filters. 68 * 69 * @return string 70 */ 71 protected function get_default_entity_name(): string { 72 return 'course'; 73 } 74 75 /** 76 * The default title for this entity in the list of columns/filters in the report builder. 77 * 78 * @return lang_string 79 */ 80 protected function get_default_entity_title(): lang_string { 81 return new lang_string('entitycourse', 'core_reportbuilder'); 82 } 83 84 /** 85 * Get custom fields helper 86 * 87 * @return custom_fields 88 */ 89 protected function get_custom_fields(): custom_fields { 90 $customfields = new custom_fields($this->get_table_alias('course') . '.id', $this->get_entity_name(), 91 'core_course', 'course'); 92 $customfields->add_joins($this->get_joins()); 93 return $customfields; 94 } 95 96 /** 97 * Initialise the entity, adding all course and custom course fields 98 * 99 * @return base 100 */ 101 public function initialise(): base { 102 $customfields = $this->get_custom_fields(); 103 104 $columns = array_merge($this->get_all_columns(), $customfields->get_columns()); 105 foreach ($columns as $column) { 106 $this->add_column($column); 107 } 108 109 $filters = array_merge($this->get_all_filters(), $customfields->get_filters()); 110 foreach ($filters as $filter) { 111 $this 112 ->add_condition($filter) 113 ->add_filter($filter); 114 } 115 116 return $this; 117 } 118 119 /** 120 * Return syntax for joining on the context table 121 * 122 * @return string 123 */ 124 public function get_context_join(): string { 125 $coursealias = $this->get_table_alias('course'); 126 $contextalias = $this->get_table_alias('context'); 127 128 return "LEFT JOIN {context} {$contextalias} 129 ON {$contextalias}.contextlevel = " . CONTEXT_COURSE . " 130 AND {$contextalias}.instanceid = {$coursealias}.id"; 131 } 132 133 /** 134 * Course fields. 135 * 136 * @return array 137 */ 138 protected function get_course_fields(): array { 139 return [ 140 'fullname' => new lang_string('fullnamecourse'), 141 'shortname' => new lang_string('shortnamecourse'), 142 'idnumber' => new lang_string('idnumbercourse'), 143 'summary' => new lang_string('coursesummary'), 144 'format' => new lang_string('format'), 145 'startdate' => new lang_string('startdate'), 146 'enddate' => new lang_string('enddate'), 147 'visible' => new lang_string('coursevisibility'), 148 'groupmode' => new lang_string('groupmode', 'group'), 149 'groupmodeforce' => new lang_string('groupmodeforce', 'group'), 150 'lang' => new lang_string('forcelanguage'), 151 'calendartype' => new lang_string('forcecalendartype', 'calendar'), 152 'theme' => new lang_string('forcetheme'), 153 'enablecompletion' => new lang_string('enablecompletion', 'completion'), 154 'downloadcontent' => new lang_string('downloadcoursecontent', 'course'), 155 ]; 156 } 157 158 /** 159 * Check if this field is sortable 160 * 161 * @param string $fieldname 162 * @return bool 163 */ 164 protected function is_sortable(string $fieldname): bool { 165 // Some columns can't be sorted, like longtext or images. 166 $nonsortable = [ 167 'summary', 168 ]; 169 170 return !in_array($fieldname, $nonsortable); 171 } 172 173 /** 174 * Return appropriate column type for given user field 175 * 176 * @param string $coursefield 177 * @return int 178 */ 179 protected function get_course_field_type(string $coursefield): int { 180 switch ($coursefield) { 181 case 'downloadcontent': 182 case 'enablecompletion': 183 case 'groupmodeforce': 184 case 'visible': 185 $fieldtype = column::TYPE_BOOLEAN; 186 break; 187 case 'startdate': 188 case 'enddate': 189 $fieldtype = column::TYPE_TIMESTAMP; 190 break; 191 case 'summary': 192 $fieldtype = column::TYPE_LONGTEXT; 193 break; 194 case 'groupmode': 195 $fieldtype = column::TYPE_INTEGER; 196 break; 197 case 'calendartype': 198 case 'idnumber': 199 case 'format': 200 case 'fullname': 201 case 'lang': 202 case 'shortname': 203 case 'theme': 204 default: 205 $fieldtype = column::TYPE_TEXT; 206 break; 207 } 208 209 return $fieldtype; 210 } 211 212 /** 213 * Return joins necessary for retrieving tags 214 * 215 * @return string[] 216 */ 217 public function get_tag_joins(): array { 218 $course = $this->get_table_alias('course'); 219 $taginstance = $this->get_table_alias('tag_instance'); 220 $tag = $this->get_table_alias('tag'); 221 222 return [ 223 "LEFT JOIN {tag_instance} {$taginstance} 224 ON {$taginstance}.component = 'core' 225 AND {$taginstance}.itemtype = 'course' 226 AND {$taginstance}.itemid = {$course}.id", 227 "LEFT JOIN {tag} {$tag} 228 ON {$tag}.id = {$taginstance}.tagid", 229 ]; 230 } 231 232 /** 233 * Returns list of all available columns. 234 * 235 * These are all the columns available to use in any report that uses this entity. 236 * 237 * @return column[] 238 */ 239 protected function get_all_columns(): array { 240 global $DB; 241 242 $coursefields = $this->get_course_fields(); 243 $tablealias = $this->get_table_alias('course'); 244 $contexttablealias = $this->get_table_alias('context'); 245 246 // Columns course full name with link, course short name with link and course id with link. 247 $fields = [ 248 'coursefullnamewithlink' => 'fullname', 249 'courseshortnamewithlink' => 'shortname', 250 'courseidnumberewithlink' => 'idnumber', 251 ]; 252 foreach ($fields as $key => $field) { 253 $column = (new column( 254 $key, 255 new lang_string($key, 'core_reportbuilder'), 256 $this->get_entity_name() 257 )) 258 ->add_joins($this->get_joins()) 259 ->set_type(column::TYPE_TEXT) 260 ->add_fields("{$tablealias}.{$field} as $key, {$tablealias}.id") 261 ->set_is_sortable(true) 262 ->add_callback(static function(?string $value, stdClass $row): string { 263 if ($value === null) { 264 return ''; 265 } 266 267 context_helper::preload_from_record($row); 268 269 return html_writer::link(course_get_url($row->id), 270 format_string($value, true, ['context' => context_course::instance($row->id)])); 271 }); 272 273 // Join on the context table so that we can use it for formatting these columns later. 274 if ($key === 'coursefullnamewithlink') { 275 $column->add_join($this->get_context_join()) 276 ->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias)); 277 } 278 279 $columns[] = $column; 280 } 281 282 foreach ($coursefields as $coursefield => $coursefieldlang) { 283 $columntype = $this->get_course_field_type($coursefield); 284 285 $columnfieldsql = "{$tablealias}.{$coursefield}"; 286 if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') { 287 $columnfieldsql = $DB->sql_order_by_text($columnfieldsql, 1024); 288 } 289 290 $column = (new column( 291 $coursefield, 292 $coursefieldlang, 293 $this->get_entity_name() 294 )) 295 ->add_joins($this->get_joins()) 296 ->set_type($columntype) 297 ->add_field($columnfieldsql, $coursefield) 298 ->add_callback([$this, 'format'], $coursefield) 299 ->set_is_sortable($this->is_sortable($coursefield)); 300 301 // Join on the context table so that we can use it for formatting these columns later. 302 if ($coursefield === 'summary' || $coursefield === 'shortname' || $coursefield === 'fullname') { 303 $column->add_join($this->get_context_join()) 304 ->add_field("{$tablealias}.id", 'courseid') 305 ->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias)); 306 } 307 308 $columns[] = $column; 309 } 310 311 return $columns; 312 } 313 314 /** 315 * Returns list of all available filters 316 * 317 * @return array 318 */ 319 protected function get_all_filters(): array { 320 global $DB; 321 322 $filters = []; 323 $tablealias = $this->get_table_alias('course'); 324 325 $fields = $this->get_course_fields(); 326 foreach ($fields as $field => $name) { 327 $filterfieldsql = "{$tablealias}.{$field}"; 328 if ($this->get_course_field_type($field) === column::TYPE_LONGTEXT) { 329 $filterfieldsql = $DB->sql_cast_to_char($filterfieldsql); 330 } 331 332 $optionscallback = [static::class, 'get_options_for_' . $field]; 333 if (is_callable($optionscallback)) { 334 $filterclass = select::class; 335 } else if ($this->get_course_field_type($field) === column::TYPE_BOOLEAN) { 336 $filterclass = boolean_select::class; 337 } else if ($this->get_course_field_type($field) === column::TYPE_TIMESTAMP) { 338 $filterclass = date::class; 339 } else { 340 $filterclass = text::class; 341 } 342 343 $filter = (new filter( 344 $filterclass, 345 $field, 346 $name, 347 $this->get_entity_name(), 348 $filterfieldsql 349 )) 350 ->add_joins($this->get_joins()); 351 352 // Populate filter options by callback, if available. 353 if (is_callable($optionscallback)) { 354 $filter->set_options_callback($optionscallback); 355 } 356 357 $filters[] = $filter; 358 } 359 360 // We add our own custom course selector filter. 361 $filters[] = (new filter( 362 course_selector::class, 363 'courseselector', 364 new lang_string('courseselect', 'core_reportbuilder'), 365 $this->get_entity_name(), 366 "{$tablealias}.id" 367 )) 368 ->add_joins($this->get_joins()); 369 370 return $filters; 371 } 372 373 /** 374 * Gets list of options if the filter supports it 375 * 376 * @param string $fieldname 377 * @return null|array 378 */ 379 protected function get_options_for(string $fieldname): ?array { 380 static $cached = []; 381 if (!array_key_exists($fieldname, $cached)) { 382 $callable = [static::class, 'get_options_for_' . $fieldname]; 383 if (is_callable($callable)) { 384 $cached[$fieldname] = $callable(); 385 } else { 386 $cached[$fieldname] = null; 387 } 388 } 389 return $cached[$fieldname]; 390 } 391 392 /** 393 * List of options for the field groupmode. 394 * 395 * @return array 396 */ 397 public static function get_options_for_groupmode(): array { 398 return [ 399 NOGROUPS => get_string('groupsnone', 'group'), 400 SEPARATEGROUPS => get_string('groupsseparate', 'group'), 401 VISIBLEGROUPS => get_string('groupsvisible', 'group'), 402 ]; 403 } 404 405 /** 406 * List of options for the field format. 407 * 408 * @return array 409 */ 410 public static function get_options_for_format(): array { 411 global $CFG; 412 require_once($CFG->dirroot.'/course/lib.php'); 413 414 $options = []; 415 416 $courseformats = get_sorted_course_formats(true); 417 foreach ($courseformats as $courseformat) { 418 $options[$courseformat] = get_string('pluginname', "format_{$courseformat}"); 419 } 420 421 return $options; 422 } 423 424 /** 425 * List of options for the field theme. 426 * 427 * @return array 428 */ 429 public static function get_options_for_theme(): array { 430 $options = []; 431 432 $themeobjects = get_list_of_themes(); 433 foreach ($themeobjects as $key => $theme) { 434 if (empty($theme->hidefromselector)) { 435 $options[$key] = get_string('pluginname', "theme_{$theme->name}"); 436 } 437 } 438 439 return $options; 440 } 441 442 /** 443 * List of options for the field lang. 444 * 445 * @return array 446 */ 447 public static function get_options_for_lang(): array { 448 return get_string_manager()->get_list_of_translations(); 449 } 450 451 /** 452 * List of options for the field. 453 * 454 * @return array 455 */ 456 public static function get_options_for_calendartype(): array { 457 return \core_calendar\type_factory::get_list_of_calendar_types(); 458 } 459 460 /** 461 * Formats the course field for display. 462 * 463 * @param mixed $value Current field value. 464 * @param stdClass $row Complete row. 465 * @param string $fieldname Name of the field to format. 466 * @return string 467 */ 468 public function format($value, stdClass $row, string $fieldname): string { 469 if ($this->get_course_field_type($fieldname) === column::TYPE_TIMESTAMP) { 470 return format::userdate($value, $row); 471 } 472 473 $options = $this->get_options_for($fieldname); 474 if ($options !== null && array_key_exists($value, $options)) { 475 return $options[$value]; 476 } 477 478 if ($this->get_course_field_type($fieldname) === column::TYPE_BOOLEAN) { 479 return format::boolean_as_text($value); 480 } 481 482 if (in_array($fieldname, ['fullname', 'shortname'])) { 483 if (!$row->courseid) { 484 return ''; 485 } 486 context_helper::preload_from_record($row); 487 $context = context_course::instance($row->courseid); 488 return format_string($value, true, ['context' => $context->id, 'escape' => false]); 489 } 490 491 if (in_array($fieldname, ['summary'])) { 492 if (!$row->courseid) { 493 return ''; 494 } 495 context_helper::preload_from_record($row); 496 $context = context_course::instance($row->courseid); 497 $summary = file_rewrite_pluginfile_urls($row->summary, 'pluginfile.php', $context->id, 'course', 'summary', null); 498 return format_text($summary); 499 } 500 501 return s($value); 502 } 503 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body