Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

declare(strict_types=1);

namespace core_reportbuilder\local\entities;

use context_course;
use context_helper;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\course_selector;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\helpers\custom_fields;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use html_writer;
use lang_string;
use stdClass;

defined('MOODLE_INTERNAL') || die();

global $CFG;
require_once($CFG->dirroot . '/course/lib.php');

/**
 * Course entity class implementation
 *
 * This entity defines all the course columns and filters to be used in any report.
 *
 * @package     core_reportbuilder
 * @copyright   2021 Sara Arjona <sara@moodle.com> based on Marina Glancy code.
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class course extends base {

    /**
     * Database tables that this entity uses and their default aliases.
     *
     * @return array
     */
    protected function get_default_table_aliases(): array {
        return [
            'course' => 'c',
            'context' => 'cctx',
> 'tag_instance' => 'cti', ]; > 'tag' => 'ct',
} /** * The default machine-readable name for this entity that will be used in the internal names of the columns/filters. * * @return string */ protected function get_default_entity_name(): string { return 'course'; } /** * The default title for this entity in the list of columns/filters in the report builder. * * @return lang_string */ protected function get_default_entity_title(): lang_string { return new lang_string('entitycourse', 'core_reportbuilder'); } /** * Get custom fields helper * * @return custom_fields */ protected function get_custom_fields(): custom_fields { $customfields = new custom_fields($this->get_table_alias('course') . '.id', $this->get_entity_name(), 'core_course', 'course'); $customfields->add_joins($this->get_joins()); return $customfields; } /** * Initialise the entity, adding all course and custom course fields * * @return base */ public function initialise(): base { $customfields = $this->get_custom_fields(); $columns = array_merge($this->get_all_columns(), $customfields->get_columns()); foreach ($columns as $column) { $this->add_column($column); } $filters = array_merge($this->get_all_filters(), $customfields->get_filters()); foreach ($filters as $filter) { $this ->add_condition($filter) ->add_filter($filter); } return $this; } /**
> * Return syntax for joining on the context table * Course fields. > * * > * @return string * @return array > */ */ > public function get_context_join(): string { protected function get_course_fields(): array { > $coursealias = $this->get_table_alias('course'); return [ > $contextalias = $this->get_table_alias('context'); 'fullname' => new lang_string('fullnamecourse'), > 'shortname' => new lang_string('shortnamecourse'), > return "LEFT JOIN {context} {$contextalias} 'idnumber' => new lang_string('idnumbercourse'), > ON {$contextalias}.contextlevel = " . CONTEXT_COURSE . " 'summary' => new lang_string('coursesummary'), > AND {$contextalias}.instanceid = {$coursealias}.id"; 'format' => new lang_string('format'), > } 'startdate' => new lang_string('startdate'), > 'enddate' => new lang_string('enddate'), > /**
'visible' => new lang_string('coursevisibility'), 'groupmode' => new lang_string('groupmode', 'group'), 'groupmodeforce' => new lang_string('groupmodeforce', 'group'), 'lang' => new lang_string('forcelanguage'), 'calendartype' => new lang_string('forcecalendartype', 'calendar'), 'theme' => new lang_string('forcetheme'), 'enablecompletion' => new lang_string('enablecompletion', 'completion'), 'downloadcontent' => new lang_string('downloadcoursecontent', 'course'), ]; } /** * Check if this field is sortable * * @param string $fieldname * @return bool */ protected function is_sortable(string $fieldname): bool { // Some columns can't be sorted, like longtext or images. $nonsortable = [ 'summary', ]; return !in_array($fieldname, $nonsortable); } /** * Return appropriate column type for given user field * * @param string $coursefield * @return int */ protected function get_course_field_type(string $coursefield): int { switch ($coursefield) { case 'downloadcontent': case 'enablecompletion': case 'groupmodeforce': case 'visible': $fieldtype = column::TYPE_BOOLEAN; break; case 'startdate': case 'enddate': $fieldtype = column::TYPE_TIMESTAMP; break; case 'summary': $fieldtype = column::TYPE_LONGTEXT; break; case 'groupmode': $fieldtype = column::TYPE_INTEGER; break; case 'calendartype': case 'idnumber': case 'format': case 'fullname': case 'lang': case 'shortname': case 'theme': default: $fieldtype = column::TYPE_TEXT; break; } return $fieldtype; } /**
> * Return joins necessary for retrieving tags * Returns list of all available columns. > * * > * @return string[] * These are all the columns available to use in any report that uses this entity. > */ * > public function get_tag_joins(): array { * @return column[] > $course = $this->get_table_alias('course'); */ > $taginstance = $this->get_table_alias('tag_instance'); protected function get_all_columns(): array { > $tag = $this->get_table_alias('tag'); global $DB; > > return [ $coursefields = $this->get_course_fields(); > "LEFT JOIN {tag_instance} {$taginstance} $tablealias = $this->get_table_alias('course'); > ON {$taginstance}.component = 'core' $contexttablealias = $this->get_table_alias('context'); > AND {$taginstance}.itemtype = 'course' > AND {$taginstance}.itemid = {$course}.id", // Columns course full name with link, course short name with link and course id with link. > "LEFT JOIN {tag} {$tag} $fields = [ > ON {$tag}.id = {$taginstance}.tagid", 'coursefullnamewithlink' => 'fullname', > ]; 'courseshortnamewithlink' => 'shortname', > } 'courseidnumberewithlink' => 'idnumber', > ]; > /**
foreach ($fields as $key => $field) { $column = (new column( $key, new lang_string($key, 'core_reportbuilder'), $this->get_entity_name() )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TEXT) ->add_fields("{$tablealias}.{$field} as $key, {$tablealias}.id") ->set_is_sortable(true) ->add_callback(static function(?string $value, stdClass $row): string { if ($value === null) { return ''; } context_helper::preload_from_record($row); return html_writer::link(course_get_url($row->id), format_string($value, true, ['context' => context_course::instance($row->id)])); }); // Join on the context table so that we can use it for formatting these columns later. if ($key === 'coursefullnamewithlink') {
< $join = "LEFT JOIN {context} {$contexttablealias} < ON {$contexttablealias}.contextlevel = " . CONTEXT_COURSE . " < AND {$contexttablealias}.instanceid = {$tablealias}.id"; < < $column->add_join($join)
> $column->add_join($this->get_context_join())
->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias)); } $columns[] = $column; } foreach ($coursefields as $coursefield => $coursefieldlang) { $columntype = $this->get_course_field_type($coursefield); $columnfieldsql = "{$tablealias}.{$coursefield}"; if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') { $columnfieldsql = $DB->sql_order_by_text($columnfieldsql, 1024); } $column = (new column( $coursefield, $coursefieldlang, $this->get_entity_name() )) ->add_joins($this->get_joins()) ->set_type($columntype) ->add_field($columnfieldsql, $coursefield) ->add_callback([$this, 'format'], $coursefield) ->set_is_sortable($this->is_sortable($coursefield)); // Join on the context table so that we can use it for formatting these columns later. if ($coursefield === 'summary' || $coursefield === 'shortname' || $coursefield === 'fullname') {
< $join = "LEFT JOIN {context} {$contexttablealias} < ON {$contexttablealias}.contextlevel = " . CONTEXT_COURSE . " < AND {$contexttablealias}.instanceid = {$tablealias}.id"; < < $column->add_join($join)
> $column->add_join($this->get_context_join())
->add_field("{$tablealias}.id", 'courseid') ->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias)); } $columns[] = $column; } return $columns; } /** * Returns list of all available filters * * @return array */ protected function get_all_filters(): array { global $DB; $filters = []; $tablealias = $this->get_table_alias('course'); $fields = $this->get_course_fields(); foreach ($fields as $field => $name) {
< // Filtering isn't supported for LONGTEXT fields on Oracle. < if ($this->get_course_field_type($field) === column::TYPE_LONGTEXT && < $DB->get_dbfamily() === 'oracle') { < < continue;
> $filterfieldsql = "{$tablealias}.{$field}"; > if ($this->get_course_field_type($field) === column::TYPE_LONGTEXT) { > $filterfieldsql = $DB->sql_cast_to_char($filterfieldsql);
} $optionscallback = [static::class, 'get_options_for_' . $field]; if (is_callable($optionscallback)) { $filterclass = select::class; } else if ($this->get_course_field_type($field) === column::TYPE_BOOLEAN) { $filterclass = boolean_select::class; } else if ($this->get_course_field_type($field) === column::TYPE_TIMESTAMP) { $filterclass = date::class; } else { $filterclass = text::class; } $filter = (new filter( $filterclass, $field, $name, $this->get_entity_name(),
< "{$tablealias}.$field"
> $filterfieldsql
)) ->add_joins($this->get_joins()); // Populate filter options by callback, if available. if (is_callable($optionscallback)) { $filter->set_options_callback($optionscallback); } $filters[] = $filter; } // We add our own custom course selector filter. $filters[] = (new filter( course_selector::class, 'courseselector', new lang_string('courseselect', 'core_reportbuilder'), $this->get_entity_name(), "{$tablealias}.id" )) ->add_joins($this->get_joins()); return $filters; } /** * Gets list of options if the filter supports it * * @param string $fieldname * @return null|array */ protected function get_options_for(string $fieldname): ?array { static $cached = []; if (!array_key_exists($fieldname, $cached)) { $callable = [static::class, 'get_options_for_' . $fieldname]; if (is_callable($callable)) { $cached[$fieldname] = $callable(); } else { $cached[$fieldname] = null; } } return $cached[$fieldname]; } /** * List of options for the field groupmode. * * @return array */ public static function get_options_for_groupmode(): array { return [ NOGROUPS => get_string('groupsnone', 'group'), SEPARATEGROUPS => get_string('groupsseparate', 'group'), VISIBLEGROUPS => get_string('groupsvisible', 'group'), ]; } /** * List of options for the field format. * * @return array */ public static function get_options_for_format(): array { global $CFG; require_once($CFG->dirroot.'/course/lib.php'); $options = []; $courseformats = get_sorted_course_formats(true); foreach ($courseformats as $courseformat) { $options[$courseformat] = get_string('pluginname', "format_{$courseformat}"); } return $options; } /** * List of options for the field theme. * * @return array */ public static function get_options_for_theme(): array { $options = []; $themeobjects = get_list_of_themes(); foreach ($themeobjects as $key => $theme) { if (empty($theme->hidefromselector)) { $options[$key] = get_string('pluginname', "theme_{$theme->name}"); } } return $options; } /** * List of options for the field lang. * * @return array */ public static function get_options_for_lang(): array { return get_string_manager()->get_list_of_translations(); } /** * List of options for the field. * * @return array */ public static function get_options_for_calendartype(): array { return \core_calendar\type_factory::get_list_of_calendar_types(); } /** * Formats the course field for display. * * @param mixed $value Current field value. * @param stdClass $row Complete row. * @param string $fieldname Name of the field to format. * @return string */ public function format($value, stdClass $row, string $fieldname): string { if ($this->get_course_field_type($fieldname) === column::TYPE_TIMESTAMP) { return format::userdate($value, $row); } $options = $this->get_options_for($fieldname); if ($options !== null && array_key_exists($value, $options)) { return $options[$value]; } if ($this->get_course_field_type($fieldname) === column::TYPE_BOOLEAN) { return format::boolean_as_text($value); } if (in_array($fieldname, ['fullname', 'shortname'])) { if (!$row->courseid) { return ''; } context_helper::preload_from_record($row); $context = context_course::instance($row->courseid); return format_string($value, true, ['context' => $context->id, 'escape' => false]); } if (in_array($fieldname, ['summary'])) { if (!$row->courseid) { return ''; } context_helper::preload_from_record($row); $context = context_course::instance($row->courseid); $summary = file_rewrite_pluginfile_urls($row->summary, 'pluginfile.php', $context->id, 'course', 'summary', null); return format_text($summary); } return s($value); } }