<?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);
}
}