bf0849d9a4
The column type determines the available aggregation options present for the column. Custom fields of type "select" should be considered as text fields for this purpose, because their stored value represents the index to their available options, rather than having any distinct meaning of it's own for display.
325 lines
12 KiB
PHP
325 lines
12 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_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);
|
|
}
|
|
|
|
/**
|
|
* Get table alias for given custom field
|
|
*
|
|
* The entity name is used to ensure the alias differs when the entity is used multiple times within the same report, each
|
|
* having their own table alias/join
|
|
*
|
|
* @param field_controller $field
|
|
* @return string
|
|
*/
|
|
private function get_table_alias(field_controller $field): string {
|
|
static $aliases = [];
|
|
|
|
$aliaskey = "{$this->entityname}_{$field->get('id')}";
|
|
if (!array_key_exists($aliaskey, $aliases)) {
|
|
$aliases[$aliaskey] = database::generate_alias();
|
|
}
|
|
|
|
return $aliases[$aliaskey];
|
|
}
|
|
|
|
/**
|
|
* Get table join for given custom field
|
|
*
|
|
* @param field_controller $field
|
|
* @return string
|
|
*/
|
|
private function get_table_join(field_controller $field): string {
|
|
$customdatatablealias = $this->get_table_alias($field);
|
|
|
|
return "LEFT JOIN {customfield_data} {$customdatatablealias}
|
|
ON {$customdatatablealias}.fieldid = {$field->get('id')}
|
|
AND {$customdatatablealias}.instanceid = {$this->tablefieldalias}";
|
|
}
|
|
|
|
/**
|
|
* Gets the custom fields columns for the report.
|
|
*
|
|
* Column will be named as 'customfield_' + customfield shortname.
|
|
*
|
|
* @return column[]
|
|
*/
|
|
public function get_columns(): array {
|
|
global $DB;
|
|
|
|
$columns = [];
|
|
|
|
$categorieswithfields = $this->handler->get_categories_with_fields();
|
|
foreach ($categorieswithfields as $fieldcategory) {
|
|
$categoryfields = $fieldcategory->get_fields();
|
|
foreach ($categoryfields as $field) {
|
|
$customdatatablealias = $this->get_table_alias($field);
|
|
|
|
$datacontroller = data_controller::create(0, null, $field);
|
|
|
|
$datafield = $datacontroller->datafield();
|
|
$datafieldsql = "{$customdatatablealias}.{$datafield}";
|
|
|
|
// Long text fields should be cast for Oracle, for aggregation support.
|
|
$columntype = $this->get_column_type($field, $datafield);
|
|
if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') {
|
|
$datafieldsql = $DB->sql_order_by_text($datafieldsql, 1024);
|
|
}
|
|
|
|
// Select enough fields to re-create and format each custom field instance value.
|
|
$selectfields = "{$customdatatablealias}.id, {$customdatatablealias}.contextid";
|
|
if ($datafield === 'value') {
|
|
// We will take the format into account when displaying the individual values.
|
|
$selectfields .= ", {$customdatatablealias}.valueformat";
|
|
}
|
|
|
|
$columns[] = (new column(
|
|
'customfield_' . $field->get('shortname'),
|
|
new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get_formatted_name()),
|
|
$this->entityname
|
|
))
|
|
->add_joins($this->get_joins())
|
|
->add_join($this->get_table_join($field))
|
|
->add_field($datafieldsql, $datafield)
|
|
->add_fields($selectfields)
|
|
->add_field($this->tablefieldalias, 'tablefieldalias')
|
|
->set_type($columntype)
|
|
->set_is_sortable($columntype !== column::TYPE_LONGTEXT)
|
|
->add_callback(static function($value, stdClass $row, field_controller $field): string {
|
|
if ($row->tablefieldalias === null) {
|
|
return '';
|
|
}
|
|
return (string) data_controller::create(0, $row, $field)->export_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));
|
|
}
|
|
}
|
|
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 ($field->get('type') === 'select') {
|
|
return column::TYPE_TEXT;
|
|
}
|
|
|
|
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 {
|
|
global $DB;
|
|
|
|
$filters = [];
|
|
|
|
$categorieswithfields = $this->handler->get_categories_with_fields();
|
|
foreach ($categorieswithfields as $fieldcategory) {
|
|
$categoryfields = $fieldcategory->get_fields();
|
|
foreach ($categoryfields as $field) {
|
|
$customdatatablealias = $this->get_table_alias($field);
|
|
|
|
$datacontroller = data_controller::create(0, null, $field);
|
|
|
|
$datafield = $datacontroller->datafield();
|
|
$datafieldsql = "{$customdatatablealias}.{$datafield}";
|
|
if ($datafield === 'value') {
|
|
$datafieldsql = $DB->sql_cast_to_char($datafieldsql);
|
|
}
|
|
|
|
$typeclass = $this->get_filter_class_type($datacontroller);
|
|
$filter = (new filter(
|
|
$typeclass,
|
|
'customfield_' . $field->get('shortname'),
|
|
new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get_formatted_name()),
|
|
$this->entityname,
|
|
$datafieldsql
|
|
))
|
|
->add_joins($this->get_joins())
|
|
->add_join($this->get_table_join($field));
|
|
|
|
// 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;
|
|
}
|
|
}
|