Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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