7a63ff9f0b
Entities are re-usable collections of report columns and filters. When creating system reports, we can re-use those elements from entities without having to know specific details about their implementation. They can be joined to reports, or other entities, using standard SQL query syntax. Define base classes, and create two example entities: course and user. Co-Authored-By: David Matamoros <davidmc@moodle.com>
465 lines
15 KiB
PHP
465 lines
15 KiB
PHP
<?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_course_category;
|
|
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',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 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_filter($filter);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Course fields.
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function get_course_fields(): array {
|
|
return [
|
|
'fullname' => new lang_string('fullnamecourse'),
|
|
'shortname' => new lang_string('shortnamecourse'),
|
|
'category' => new lang_string('coursecategory'),
|
|
'idnumber' => new lang_string('idnumbercourse'),
|
|
'summary' => new lang_string('coursesummary'),
|
|
'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 'category':
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Returns list of all available columns.
|
|
*
|
|
* These are all the columns available to use in any report that uses this entity.
|
|
*
|
|
* @return column[]
|
|
*/
|
|
protected function get_all_columns(): array {
|
|
$columns = [];
|
|
$coursefields = $this->get_course_fields();
|
|
$tablealias = $this->get_table_alias('course');
|
|
$contexttablealias = $this->get_table_alias('context');
|
|
|
|
// Columns course full name with link, course short name with link and course id with link.
|
|
$fields = [
|
|
'coursefullnamewithlink' => 'fullname',
|
|
'courseshortnamewithlink' => 'shortname',
|
|
'courseidnumberewithlink' => 'idnumber',
|
|
];
|
|
foreach ($fields as $key => $field) {
|
|
$columns[] = (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 '';
|
|
}
|
|
|
|
return html_writer::link(course_get_url($row->id), $value);
|
|
});
|
|
}
|
|
|
|
foreach ($coursefields as $coursefield => $coursefieldlang) {
|
|
$column = (new column(
|
|
$coursefield,
|
|
$coursefieldlang,
|
|
$this->get_entity_name()
|
|
))
|
|
->add_joins($this->get_joins())
|
|
->set_type($this->get_course_field_type($coursefield))
|
|
->add_field("$tablealias.$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)
|
|
->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;
|
|
}
|
|
|
|
$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"
|
|
))
|
|
->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('courses'),
|
|
$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 category.
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function get_options_for_category(): array {
|
|
return core_course_category::make_categories_list('moodle/category:viewcourselist');
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|