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>
297 lines
11 KiB
PHP
297 lines
11 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\helpers;
|
|
|
|
use core_reportbuilder\local\filters\boolean_select;
|
|
use core_reportbuilder\local\filters\date;
|
|
use core_reportbuilder\local\filters\number;
|
|
use core_reportbuilder\local\filters\select;
|
|
use core_reportbuilder\local\filters\text;
|
|
use core_reportbuilder\local\report\column;
|
|
use core_reportbuilder\local\report\filter;
|
|
use lang_string;
|
|
use stdClass;
|
|
use core_customfield\data;
|
|
use core_customfield\data_controller;
|
|
use core_customfield\field_controller;
|
|
use core_customfield\handler;
|
|
|
|
/**
|
|
* Helper class for course custom fields.
|
|
*
|
|
* @package core_reportbuilder
|
|
* @copyright 2021 Sara Arjona <sara@moodle.com> based on David Matamoros <davidmc@moodle.com> code.
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class custom_fields {
|
|
|
|
/** @var string $entityname Name of the entity */
|
|
private $entityname;
|
|
|
|
/** @var handler $handler The handler for the customfields */
|
|
private $handler;
|
|
|
|
/** @var int $tablefieldalias The table alias and the field name (table.field) that matches the customfield instanceid. */
|
|
private $tablefieldalias;
|
|
|
|
/** @var array additional joins */
|
|
private $joins = [];
|
|
|
|
/**
|
|
* Class customfields constructor.
|
|
*
|
|
* @param string $tablefieldalias table alias and the field name (table.field) that matches the customfield instanceid.
|
|
* @param string $entityname name of the entity in the report where we add custom fields.
|
|
* @param string $component component name of full frankenstyle plugin name.
|
|
* @param string $area name of the area (each component/plugin may define handlers for multiple areas).
|
|
* @param int $itemid item id if the area uses them (usually not used).
|
|
*/
|
|
public function __construct(string $tablefieldalias, string $entityname, string $component, string $area, int $itemid = 0) {
|
|
$this->tablefieldalias = $tablefieldalias;
|
|
$this->entityname = $entityname;
|
|
$this->handler = handler::get_handler($component, $area, $itemid);
|
|
}
|
|
|
|
/**
|
|
* Additional join that is needed.
|
|
*
|
|
* @param string $join
|
|
* @return self
|
|
*/
|
|
public function add_join(string $join): self {
|
|
$this->joins[trim($join)] = trim($join);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Additional joins that are needed.
|
|
*
|
|
* @param array $joins
|
|
* @return self
|
|
*/
|
|
public function add_joins(array $joins): self {
|
|
foreach ($joins as $join) {
|
|
$this->add_join($join);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Return joins
|
|
*
|
|
* @return string[]
|
|
*/
|
|
private function get_joins(): array {
|
|
return array_values($this->joins);
|
|
}
|
|
|
|
/**
|
|
* Gets the custom fields columns for the report.
|
|
*
|
|
* Column will be named as 'customfield_' + customfield shortname.
|
|
*
|
|
* @return column[]
|
|
*/
|
|
public function get_columns(): array {
|
|
$columns = [];
|
|
|
|
$categorieswithfields = $this->handler->get_categories_with_fields();
|
|
foreach ($categorieswithfields as $fieldcategory) {
|
|
$categoryfields = $fieldcategory->get_fields();
|
|
foreach ($categoryfields as $field) {
|
|
$customdatatablealias = database::generate_alias();
|
|
|
|
$datacontroller = data_controller::create(0, null, $field);
|
|
$datafield = $datacontroller->datafield();
|
|
|
|
$alias = 'customfield_' . $field->get('shortname');
|
|
$selectfields = "{$customdatatablealias}.{$datafield} as {$alias}, {$customdatatablealias}.id";
|
|
if ($datafield === 'value') {
|
|
// We will take the format into account when displaying the individual values.
|
|
$selectfields .= ", {$customdatatablealias}.valueformat";
|
|
}
|
|
|
|
$columnname = $field->get_formatted_name();
|
|
$columntype = $this->get_column_type($field, $datafield);
|
|
|
|
$newcolumn = (new column(
|
|
'customfield_' . $field->get('shortname'),
|
|
new lang_string('customfieldcolumn', 'core_reportbuilder', $columnname),
|
|
$this->entityname
|
|
))
|
|
->add_joins($this->get_joins())
|
|
->add_join("LEFT JOIN {customfield_data} {$customdatatablealias} " .
|
|
"ON {$customdatatablealias}.fieldid = " . $field->get('id') . " " .
|
|
"AND {$customdatatablealias}.instanceid = {$this->tablefieldalias}")
|
|
->add_fields($selectfields)
|
|
->set_type($columntype)
|
|
->set_is_sortable($columntype !== column::TYPE_LONGTEXT)
|
|
->add_callback([$this, 'customfield_value'], $field)
|
|
// Important. If the handler implements can_view() function, it will be called with parameter $instanceid=0.
|
|
// This means that per-instance access validation will be ignored.
|
|
->set_is_available($this->handler->can_view($field, 0));
|
|
|
|
$columns[] = $newcolumn;
|
|
}
|
|
}
|
|
return $columns;
|
|
}
|
|
|
|
/**
|
|
* Returns the column type
|
|
*
|
|
* @param field_controller $field
|
|
* @param string $datafield
|
|
* @return int
|
|
*/
|
|
private function get_column_type(field_controller $field, string $datafield): int {
|
|
if ($field->get('type') === 'checkbox') {
|
|
return column::TYPE_BOOLEAN;
|
|
}
|
|
|
|
if ($field->get('type') === 'date') {
|
|
return column::TYPE_TIMESTAMP;
|
|
}
|
|
|
|
if ($datafield === 'intvalue') {
|
|
return column::TYPE_INTEGER;
|
|
}
|
|
|
|
if ($datafield === 'decvalue') {
|
|
return column::TYPE_FLOAT;
|
|
}
|
|
|
|
if ($datafield === 'value') {
|
|
return column::TYPE_LONGTEXT;
|
|
}
|
|
|
|
return column::TYPE_TEXT;
|
|
}
|
|
|
|
/**
|
|
* Returns all available filters on custom fields.
|
|
*
|
|
* Filter will be named as 'customfield_' + customfield shortname.
|
|
*
|
|
* @return filter[]
|
|
*/
|
|
public function get_filters(): array {
|
|
$filters = [];
|
|
|
|
$categorieswithfields = $this->handler->get_categories_with_fields();
|
|
foreach ($categorieswithfields as $fieldcategory) {
|
|
$categoryfields = $fieldcategory->get_fields();
|
|
foreach ($categoryfields as $field) {
|
|
$customdatatablealias = database::generate_alias();
|
|
|
|
$datacontroller = data_controller::create(0, null, $field);
|
|
$datafield = $datacontroller->datafield();
|
|
$typeclass = $this->get_filter_class_type($datacontroller);
|
|
|
|
$filter = (new filter(
|
|
$typeclass,
|
|
'customfield_' . $field->get('shortname'),
|
|
new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get('name')),
|
|
$this->entityname,
|
|
"{$customdatatablealias}.{$datafield}"
|
|
))
|
|
->add_joins($this->get_joins())
|
|
->add_join("LEFT JOIN {customfield_data} {$customdatatablealias} " .
|
|
"ON {$customdatatablealias}.fieldid = " . $field->get('id') . " " .
|
|
"AND {$customdatatablealias}.instanceid = {$this->tablefieldalias}");
|
|
|
|
// Options are stored inside configdata json string and we need to convert it to array.
|
|
if ($field->get('type') === 'select') {
|
|
$filter->set_options_callback(static function() use ($field): array {
|
|
$options = explode("\r\n", $field->get_configdata_property('options'));
|
|
// Method set_options starts using array at index 1. we shift one position on this array.
|
|
// In course settings this menu has an empty option and we need to respect that.
|
|
array_unshift($options, " ");
|
|
unset($options[0]);
|
|
return $options;
|
|
});
|
|
}
|
|
|
|
$filters[] = $filter;
|
|
}
|
|
}
|
|
return $filters;
|
|
}
|
|
|
|
/**
|
|
* Returns class for the filter element that should be used for the field
|
|
*
|
|
* In some situation we can assume what kind of data is stored in the customfield plugin and we can
|
|
* display appropriate filter form element. For all others assume text filter.
|
|
*
|
|
* @param data_controller $datacontroller
|
|
* @return string
|
|
*/
|
|
private function get_filter_class_type(data_controller $datacontroller): string {
|
|
$type = $datacontroller->get_field()->get('type');
|
|
|
|
switch ($type) {
|
|
case 'checkbox':
|
|
$classtype = boolean_select::class;
|
|
break;
|
|
case 'date':
|
|
$classtype = date::class;
|
|
break;
|
|
case 'select':
|
|
$classtype = select::class;
|
|
break;
|
|
default:
|
|
// To support third party field type we need to account for stored numbers.
|
|
$datafield = $datacontroller->datafield();
|
|
if ($datafield === 'intvalue' || $datafield === 'decvalue') {
|
|
$classtype = number::class;
|
|
} else {
|
|
$classtype = text::class;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return $classtype;
|
|
}
|
|
|
|
/**
|
|
* Format for custom fields value. We get the correct custom field value using export_value method.
|
|
*
|
|
* @param mixed $value Current value.
|
|
* @param stdClass $row Full row.
|
|
* @param field_controller $field Field controller object.
|
|
* @return mixed|null
|
|
*/
|
|
public function customfield_value($value, stdClass $row, field_controller $field) {
|
|
$defaults = [
|
|
'id' => -1,
|
|
'shortcharvalue' => $value,
|
|
'charvalue' => $value,
|
|
'intvalue' => (int)$value,
|
|
'decvalue' => (float)$value,
|
|
'value' => $value,
|
|
'fieldid' => $field->get('id'),
|
|
'valueformat' => FORMAT_HTML,
|
|
];
|
|
$row = array_intersect_key(array_merge($defaults, (array)$row), data::properties_definition());
|
|
$data = data_controller::create(0, (object)$row, $field);
|
|
return $data->export_value();
|
|
}
|
|
}
|