Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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  }