first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+442
View File
@@ -0,0 +1,442 @@
<?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;
use coding_exception;
use core_reportbuilder\local\helpers\report;
use core_reportbuilder\local\models\column as column_model;
use core_reportbuilder\local\models\filter as filter_model;
use core_reportbuilder\local\report\base;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* Class datasource
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class datasource extends base {
/** @var float[] $elementsmodified Track the time elements of specific reports have been added, updated, removed */
private static $elementsmodified = [];
/** @var array $activecolumns */
private $activecolumns;
/** @var array $activefilters */
private $activefilters;
/** @var array $activeconditions */
private $activeconditions;
/**
* Add columns from the given entity name to be available to use in a custom report
*
* Wildcard matching is supported with '*' in both $include and $exclude, e.g. ['customfield*']
*
* @param string $entityname
* @param string[] $include Include only these columns, if omitted then include all
* @param string[] $exclude Exclude these columns, if omitted then exclude none
* @throws coding_exception If both $include and $exclude are non-empty
*/
final protected function add_columns_from_entity(string $entityname, array $include = [], array $exclude = []): void {
if (!empty($include) && !empty($exclude)) {
throw new coding_exception('Cannot specify columns to include and exclude simultaneously');
}
$entity = $this->get_entity($entityname);
// Retrieve filtered columns from entity, respecting given $include/$exclude parameters.
$columns = array_filter($entity->get_columns(), function(column $column) use ($include, $exclude): bool {
if (!empty($include)) {
return $this->report_element_search($column->get_name(), $include);
}
if (!empty($exclude)) {
return !$this->report_element_search($column->get_name(), $exclude);
}
return true;
});
foreach ($columns as $column) {
$this->add_column($column);
}
}
/**
* Add default datasource columns to the report
*
* Uses column data returned by the source {@see get_default_columns} and {@see get_default_column_sorting} methods
*
* @throws coding_exception If default column sorting refers to an invalid column
*/
public function add_default_columns(): void {
$reportid = $this->get_report_persistent()->get('id');
// Retrieve default column sorting, and track index of both sorted/non-sorted columns.
$columnidentifiers = $this->get_default_columns();
$defaultcolumnsorting = $this->get_default_column_sorting();
$defaultcolumnsortinginvalid = array_diff_key($defaultcolumnsorting,
array_fill_keys($columnidentifiers, 1));
if (count($defaultcolumnsortinginvalid) > 0) {
throw new coding_exception('Invalid column name', array_key_first($defaultcolumnsortinginvalid));
}
$columnnonsortingindex = count($defaultcolumnsorting) + 1;
foreach ($columnidentifiers as $uniqueidentifier) {
$column = report::add_report_column($reportid, $uniqueidentifier);
// After adding the column, toggle sorting according to defaults provided by the datasource.
$sortorder = array_search($uniqueidentifier, array_keys($defaultcolumnsorting));
if ($sortorder !== false) {
$column->set_many([
'sortenabled' => true,
'sortdirection' => $defaultcolumnsorting[$uniqueidentifier],
'sortorder' => $sortorder + 1,
])->update();
} else if (!empty($defaultcolumnsorting)) {
$column->set('sortorder', $columnnonsortingindex++)->update();
}
}
}
/**
* Return the default columns that will be added to the report upon creation, by {@see add_default_columns}
*
* @return string[]
*/
abstract public function get_default_columns(): array;
/**
* Return the default column sorting that will be set for the report upon creation, by {@see add_default_columns}
*
* When overriding this method in child classes, column identifiers specified must refer to default columns returned from
* the {@see get_default_columns} method
*
* @return int[] array [column identifier => SORT_ASC/SORT_DESC]
*/
public function get_default_column_sorting(): array {
return [];
}
/**
* Override parent method, returning only those columns specifically added to the custom report (rather than all that are
* available)
*
* @return column[]
*/
public function get_active_columns(): array {
$reportid = $this->get_report_persistent()->get('id');
// Determine whether we already retrieved the columns since the report was last modified.
self::$elementsmodified += [$reportid => -1];
if ($this->activecolumns !== null && $this->activecolumns['builttime'] > self::$elementsmodified[$reportid]) {
return $this->activecolumns['values'];
}
$this->activecolumns = ['builttime' => microtime(true), 'values' => []];
$activecolumns = column_model::get_records(['reportid' => $reportid], 'columnorder');
foreach ($activecolumns as $index => $column) {
$instance = $this->get_column($column->get('uniqueidentifier'));
// Ensure the column is still present and available.
if ($instance !== null && $instance->get_is_available()) {
if ($instance->get_is_deprecated()) {
debugging("The column '{$instance->get_unique_identifier()}' is deprecated, please do not use it any more." .
" {$instance->get_is_deprecated_message()}", DEBUG_DEVELOPER);
}
// We should clone the report column to ensure if it's added twice to a report, each operates independently.
$this->activecolumns['values'][] = clone $instance
->set_index($index)
->set_persistent($column)
->set_aggregation($column->get('aggregation'));
}
}
return $this->activecolumns['values'];
}
/**
* Add filters from the given entity name to be available to use in a custom report
*
* Wildcard matching is supported with '*' in both $include and $exclude, e.g. ['customfield*']
*
* @param string $entityname
* @param string[] $include Include only these filters, if omitted then include all
* @param string[] $exclude Exclude these filters, if omitted then exclude none
* @throws coding_exception If both $include and $exclude are non-empty
*/
final protected function add_filters_from_entity(string $entityname, array $include = [], array $exclude = []): void {
if (!empty($include) && !empty($exclude)) {
throw new coding_exception('Cannot specify filters to include and exclude simultaneously');
}
$entity = $this->get_entity($entityname);
// Retrieve filtered filters from entity, respecting given $include/$exclude parameters.
$filters = array_filter($entity->get_filters(), function(filter $filter) use ($include, $exclude): bool {
if (!empty($include)) {
return $this->report_element_search($filter->get_name(), $include);
}
if (!empty($exclude)) {
return !$this->report_element_search($filter->get_name(), $exclude);
}
return true;
});
foreach ($filters as $filter) {
$this->add_filter($filter);
}
}
/**
* Add default datasource filters to the report
*
* This method is optional and can be called when the report is created to add the default filters defined in the
* selected datasource.
*/
public function add_default_filters(): void {
$reportid = $this->get_report_persistent()->get('id');
$filteridentifiers = $this->get_default_filters();
foreach ($filteridentifiers as $uniqueidentifier) {
report::add_report_filter($reportid, $uniqueidentifier);
}
}
/**
* Return the filters that will be added to the report once is created
*
* @return string[]
*/
abstract public function get_default_filters(): array;
/**
* Override parent method, returning only those filters specifically added to the custom report (rather than all that are
* available)
*
* @return filter[]
*/
public function get_active_filters(): array {
$reportid = $this->get_report_persistent()->get('id');
// Determine whether we already retrieved the filters since the report was last modified.
self::$elementsmodified += [$reportid => -1];
if ($this->activefilters !== null && $this->activefilters['builttime'] > self::$elementsmodified[$reportid]) {
return $this->activefilters['values'];
}
$this->activefilters = ['builttime' => microtime(true), 'values' => []];
$activefilters = filter_model::get_filter_records($reportid, 'filterorder');
foreach ($activefilters as $filter) {
$instance = $this->get_filter($filter->get('uniqueidentifier'));
// Ensure the filter is still present and available.
if ($instance !== null && $instance->get_is_available()) {
if ($instance->get_is_deprecated()) {
debugging("The filter '{$instance->get_unique_identifier()}' is deprecated, please do not use it any more." .
" {$instance->get_is_deprecated_message()}", DEBUG_DEVELOPER);
}
$this->activefilters['values'][$instance->get_unique_identifier()] =
$instance->set_persistent($filter);
}
}
return $this->activefilters['values'];
}
/**
* Add conditions from the given entity name to be available to use in a custom report
*
* Wildcard matching is supported with '*' in both $include and $exclude, e.g. ['customfield*']
*
* @param string $entityname
* @param string[] $include Include only these conditions, if omitted then include all
* @param string[] $exclude Exclude these conditions, if omitted then exclude none
* @throws coding_exception If both $include and $exclude are non-empty
*/
final protected function add_conditions_from_entity(string $entityname, array $include = [], array $exclude = []): void {
if (!empty($include) && !empty($exclude)) {
throw new coding_exception('Cannot specify conditions to include and exclude simultaneously');
}
$entity = $this->get_entity($entityname);
// Retrieve filtered conditions from entity, respecting given $include/$exclude parameters.
$conditions = array_filter($entity->get_conditions(), function(filter $condition) use ($include, $exclude): bool {
if (!empty($include)) {
return $this->report_element_search($condition->get_name(), $include);
}
if (!empty($exclude)) {
return !$this->report_element_search($condition->get_name(), $exclude);
}
return true;
});
foreach ($conditions as $condition) {
$this->add_condition($condition);
}
}
/**
* Add default datasource conditions to the report
*
* This method is optional and can be called when the report is created to add the default conditions defined in the
* selected datasource.
*/
public function add_default_conditions(): void {
$reportid = $this->get_report_persistent()->get('id');
$conditionidentifiers = $this->get_default_conditions();
foreach ($conditionidentifiers as $uniqueidentifier) {
report::add_report_condition($reportid, $uniqueidentifier);
}
// Set the default condition values if they have been set in the datasource.
$this->set_condition_values($this->get_default_condition_values());
}
/**
* Return the conditions that will be added to the report once is created
*
* @return string[]
*/
abstract public function get_default_conditions(): array;
/**
* Return the default condition values that will be added to the report once is created
*
* For any of the default conditions returned by the method {@see get_default_conditions} is
* possible to set the initial values.
*
* @return array
*/
public function get_default_condition_values(): array {
return [];
}
/**
* Override parent method, returning only those conditions specifically added to the custom report (rather than all that are
* available)
*
* @return filter[]
*/
public function get_active_conditions(): array {
$reportid = $this->get_report_persistent()->get('id');
// Determine whether we already retrieved the conditions since the report was last modified.
self::$elementsmodified += [$reportid => -1];
if ($this->activeconditions !== null && $this->activeconditions['builttime'] > self::$elementsmodified[$reportid]) {
return $this->activeconditions['values'];
}
$this->activeconditions = ['builttime' => microtime(true), 'values' => []];
$activeconditions = filter_model::get_condition_records($reportid, 'filterorder');
foreach ($activeconditions as $condition) {
$instance = $this->get_condition($condition->get('uniqueidentifier'));
// Ensure the condition is still present and available.
if ($instance !== null && $instance->get_is_available()) {
if ($instance->get_is_deprecated()) {
debugging("The condition '{$instance->get_unique_identifier()}' is deprecated, please do not use it any more." .
" {$instance->get_is_deprecated_message()}", DEBUG_DEVELOPER);
}
$this->activeconditions['values'][$instance->get_unique_identifier()] =
$instance->set_persistent($condition);
}
}
return $this->activeconditions['values'];
}
/**
* Adds all columns/filters/conditions from the given entity to the report at once
*
* @param string $entityname
* @param string[] $limitcolumns Include only these columns
* @param string[] $limitfilters Include only these filters
* @param string[] $limitconditions Include only these conditions
*/
final protected function add_all_from_entity(
string $entityname,
array $limitcolumns = [],
array $limitfilters = [],
array $limitconditions = [],
): void {
$this->add_columns_from_entity($entityname, $limitcolumns);
$this->add_filters_from_entity($entityname, $limitfilters);
$this->add_conditions_from_entity($entityname, $limitconditions);
}
/**
* Adds all columns/filters/conditions from all the entities added to the report at once
*/
final protected function add_all_from_entities(): void {
foreach ($this->get_entities() as $entity) {
$this->add_all_from_entity($entity->get_entity_name());
}
}
/**
* Indicate that report elements have been modified, e.g. columns/filters/conditions have been added, removed or updated
*
* @param int $reportid
*/
final public static function report_elements_modified(int $reportid): void {
self::$elementsmodified[$reportid] = microtime(true);
}
/**
* Search for given element within list of search items, supporting '*' wildcards
*
* @param string $element
* @param string[] $search
* @return bool
*/
private function report_element_search(string $element, array $search): bool {
foreach ($search as $item) {
// Simple matching.
if ($element === $item) {
return true;
}
// Wildcard matching.
if (strpos($item, '*') !== false) {
$pattern = '/^' . str_replace('\*', '.*', preg_quote($item)) . '$/';
return (bool) preg_match($pattern, $element);
}
}
return false;
}
}
@@ -0,0 +1,111 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\audience;
use moodle_url;
/**
* Report builder custom report audience created event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int reportid: The id of the report
* }
*/
class audience_created extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = audience::TABLE;
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report audience object
*
* @param audience $audience
* @return self
*/
public static function create_from_object(audience $audience): self {
$eventparams = [
'context' => $audience->get_report()->get_context(),
'objectid' => $audience->get('id'),
'other' => [
'reportid' => $audience->get('reportid'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $audience->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('audiencecreated', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
$reportid = $this->other['reportid'];
return "The user with id '$this->userid' created an audience in the custom report with id '$reportid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
if (!isset($this->other['reportid'])) {
throw new coding_exception('The \'reportid\' must be set in other.');
}
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->other['reportid']], 'audience');
}
}
@@ -0,0 +1,112 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\audience;
use moodle_url;
/**
* Report builder custom report audience deleted event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int reportid: The id of the report
* }
*/
class audience_deleted extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = audience::TABLE;
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report audience object
*
* @param audience $audience
* @return self
*/
public static function create_from_object(audience $audience): self {
$eventparams = [
'context' => $audience->get_report()->get_context(),
'objectid' => $audience->get('id'),
'other' => [
'reportid' => $audience->get('reportid'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $audience->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('audiencedeletedevent', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
$reportid = $this->other['reportid'];
return "The user with id '$this->userid' deleted the audience with id '$this->objectid' in the custom report" .
" with id '$reportid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
if (!isset($this->other['reportid'])) {
throw new coding_exception('The \'reportid\' must be set in other.');
}
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->other['reportid']], 'audience');
}
}
@@ -0,0 +1,112 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\audience;
use moodle_url;
/**
* Report builder custom report audience created event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int reportid: The id of the report
* }
*/
class audience_updated extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = audience::TABLE;
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report audience object
*
* @param audience $audience
* @return self
*/
public static function create_from_object(audience $audience): self {
$eventparams = [
'context' => $audience->get_report()->get_context(),
'objectid' => $audience->get('id'),
'other' => [
'reportid' => $audience->get('reportid'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $audience->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('audienceupdated', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
$reportid = $this->other['reportid'];
return "The user with id '$this->userid' updated the audience with id '$this->objectid' in the custom report" .
" with id '$reportid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
if (!isset($this->other['reportid'])) {
throw new coding_exception('The \'reportid\' must be set in other.');
}
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->other['reportid']], 'audience');
}
}
@@ -0,0 +1,109 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\report;
use moodle_url;
/**
* Report builder custom report created event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - string name: The name of the report
* - string source: The report source class
* }
*/
class report_created extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = report::TABLE;
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report object
*
* @param report $report
* @return self
*/
public static function create_from_object(report $report): self {
$eventparams = [
'context' => $report->get_context(),
'objectid' => $report->get('id'),
'other' => [
'name' => $report->get('name'),
'source' => $report->get('source'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $report->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('reportcreated', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' created the custom report with id '$this->objectid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->objectid]);
}
}
@@ -0,0 +1,99 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\report;
/**
* Report builder custom report deleted event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - string name: The name of the report
* - string source: The report source class
* }
*/
class report_deleted extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = report::TABLE;
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report object
*
* @param report $report
* @return self
*/
public static function create_from_object(report $report): self {
$eventparams = [
'context' => $report->get_context(),
'objectid' => $report->get('id'),
'other' => [
'name' => $report->get('name'),
'source' => $report->get('source'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $report->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('reportdeleted', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' deleted the custom report with id '$this->objectid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
}
}
@@ -0,0 +1,109 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\report;
use moodle_url;
/**
* Report builder custom report updated event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - string name: The name of the report
* - string source: The report source class
* }
*/
class report_updated extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = report::TABLE;
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report object
*
* @param report $report
* @return self
*/
public static function create_from_object(report $report): self {
$eventparams = [
'context' => $report->get_context(),
'objectid' => $report->get('id'),
'other' => [
'name' => $report->get('name'),
'source' => $report->get('source'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $report->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('reportupdated', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' updated the custom report with id '$this->objectid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->objectid]);
}
}
@@ -0,0 +1,109 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\report;
use moodle_url;
/**
* Report builder custom report viewed event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - string name: The name of the report
* - string source: The report source class
* }
*/
class report_viewed extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = report::TABLE;
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report object
*
* @param report $report
* @return self
*/
public static function create_from_object(report $report): self {
$eventparams = [
'context' => $report->get_context(),
'objectid' => $report->get('id'),
'other' => [
'name' => $report->get('name'),
'source' => $report->get('source'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $report->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('reportviewed', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the custom report with id '$this->objectid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/view.php', ['id' => $this->objectid]);
}
}
@@ -0,0 +1,111 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\schedule;
use moodle_url;
/**
* Report builder custom report schedule created event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int reportid: The id of the report
* }
*/
class schedule_created extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = schedule::TABLE;
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report schedule object
*
* @param schedule $schedule
* @return self
*/
public static function create_from_object(schedule $schedule): self {
$eventparams = [
'context' => $schedule->get_report()->get_context(),
'objectid' => $schedule->get('id'),
'other' => [
'reportid' => $schedule->get('reportid'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $schedule->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('schedulecreated', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
$reportid = $this->other['reportid'];
return "The user with id '$this->userid' created a schedule in the custom report with id '$reportid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
if (!isset($this->other['reportid'])) {
throw new coding_exception('The \'reportid\' must be set in other.');
}
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->other['reportid']], 'schedules');
}
}
@@ -0,0 +1,112 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\schedule;
use moodle_url;
/**
* Report builder custom report schedule created event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int reportid: The id of the report
* }
*/
class schedule_deleted extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = schedule::TABLE;
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report schedule object
*
* @param schedule $schedule
* @return self
*/
public static function create_from_object(schedule $schedule): self {
$eventparams = [
'context' => $schedule->get_report()->get_context(),
'objectid' => $schedule->get('id'),
'other' => [
'reportid' => $schedule->get('reportid'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $schedule->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('scheduledeleted', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
$reportid = $this->other['reportid'];
return "The user with id '$this->userid' deleted the schedule with id '$this->objectid' in the custom report" .
" with id '$reportid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
if (!isset($this->other['reportid'])) {
throw new coding_exception('The \'reportid\' must be set in other.');
}
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->other['reportid']], 'schedules');
}
}
@@ -0,0 +1,112 @@
<?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\event;
use coding_exception;
use core\event\base;
use core_reportbuilder\local\models\schedule;
use moodle_url;
/**
* Report builder custom report schedule updated event class.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @property-read array $other {
* Extra information about the event.
*
* - int reportid: The id of the report
* }
*/
class schedule_updated extends base {
/**
* Initialise the event data.
*/
protected function init() {
$this->data['objecttable'] = schedule::TABLE;
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Creates an instance from a report schedule object
*
* @param schedule $schedule
* @return self
*/
public static function create_from_object(schedule $schedule): self {
$eventparams = [
'context' => $schedule->get_report()->get_context(),
'objectid' => $schedule->get('id'),
'other' => [
'reportid' => $schedule->get('reportid'),
]
];
$event = self::create($eventparams);
$event->add_record_snapshot($event->objecttable, $schedule->to_record());
return $event;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('scheduleupdated', 'core_reportbuilder');
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
$reportid = $this->other['reportid'];
return "The user with id '$this->userid' updated the schedule with id '$this->objectid' in the custom report" .
" with id '$reportid'.";
}
/**
* Custom validations.
*
* @throws coding_exception
*/
protected function validate_data(): void {
parent::validate_data();
if (!isset($this->objectid)) {
throw new coding_exception('The \'objectid\' must be set.');
}
if (!isset($this->other['reportid'])) {
throw new coding_exception('The \'reportid\' must be set in other.');
}
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->other['reportid']], 'schedules');
}
}
+90
View File
@@ -0,0 +1,90 @@
<?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\external\audiences;
use core_reportbuilder\local\audiences\base;
use core_external\external_api;
use core_external\external_value;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
/**
* External method for deleting a report audience
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete extends external_api {
/**
* Describes the parameters for get_users_courses.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters(
[
'reportid' => new external_value(PARAM_INT, 'Report id'),
'instanceid' => new external_value(PARAM_INT, 'Audience instance id'),
]
);
}
/**
* External function to delete a report audience instance.
*
* @param int $reportid
* @param int $instanceid
* @return bool
*/
public static function execute(int $reportid, int $instanceid): bool {
[
'reportid' => $reportid,
'instanceid' => $instanceid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'instanceid' => $instanceid,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
$baseinstance = base::instance($instanceid);
if ($baseinstance && $baseinstance->user_can_edit()) {
$persistent = $baseinstance->get_persistent();
$persistent->delete();
return true;
}
return false;
}
/**
* Describes the data returned from the external function.
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL, '', VALUE_REQUIRED);
}
}
+91
View File
@@ -0,0 +1,91 @@
<?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\external\columns;
use core_reportbuilder\external\custom_report_columns_sorting_exporter;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\report;
/**
* External method for adding report columns
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'uniqueidentifier' => new external_value(PARAM_RAW, 'Unique identifier of the column'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param string $uniqueidentifier
* @return array
*/
public static function execute(int $reportid, string $uniqueidentifier): array {
global $PAGE;
[
'reportid' => $reportid,
'uniqueidentifier' => $uniqueidentifier,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'uniqueidentifier' => $uniqueidentifier,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::add_report_column($reportid, $uniqueidentifier);
$exporter = new custom_report_columns_sorting_exporter(null, [
'report' => $report,
]);
return (array) $exporter->export($PAGE->get_renderer('core'));
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return custom_report_columns_sorting_exporter::get_read_structure();
}
}
+91
View File
@@ -0,0 +1,91 @@
<?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\external\columns;
use core_reportbuilder\external\custom_report_columns_sorting_exporter;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\report;
/**
* External method for deleting report columns
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'columnid' => new external_value(PARAM_INT, 'Column ID'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $columnid
* @return array
*/
public static function execute(int $reportid, int $columnid): array {
global $PAGE;
[
'reportid' => $reportid,
'columnid' => $columnid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'columnid' => $columnid,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::delete_report_column($reportid, $columnid);
$exporter = new custom_report_columns_sorting_exporter(null, [
'report' => $report,
]);
return (array) $exporter->export($PAGE->get_renderer('core'));
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return custom_report_columns_sorting_exporter::get_read_structure();
}
}
+85
View File
@@ -0,0 +1,85 @@
<?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\external\columns;
use core_external\external_api;
use core_external\external_value;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\report;
/**
* External method for re-ordering report columns
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reorder extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'columnid' => new external_value(PARAM_INT, 'Column ID'),
'position' => new external_value(PARAM_INT, 'New column position')
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $columnid
* @param int $position
* @return bool
*/
public static function execute(int $reportid, int $columnid, int $position): bool {
[
'reportid' => $reportid,
'columnid' => $columnid,
'position' => $position,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'columnid' => $columnid,
'position' => $position,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
return report::reorder_report_column($reportid, $columnid, $position);
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL, 'Success');
}
}
+84
View File
@@ -0,0 +1,84 @@
<?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\external\columns\sort;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\external\custom_report_columns_sorting_exporter;
/**
* External method for retrieving report column sorting
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @return array
*/
public static function execute(int $reportid): array {
global $PAGE;
[
'reportid' => $reportid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
$exporter = new custom_report_columns_sorting_exporter(null, [
'report' => $report,
]);
return (array) $exporter->export($PAGE->get_renderer('core'));
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return custom_report_columns_sorting_exporter::get_read_structure();
}
}
+95
View File
@@ -0,0 +1,95 @@
<?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\external\columns\sort;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\report;
use core_reportbuilder\external\custom_report_columns_sorting_exporter;
/**
* External method for re-ordering report column sorting
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reorder extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'columnid' => new external_value(PARAM_INT, 'Column ID'),
'position' => new external_value(PARAM_INT, 'New column sort position'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $columnid
* @param int $position
* @return array
*/
public static function execute(int $reportid, int $columnid, int $position): array {
global $PAGE;
[
'reportid' => $reportid,
'columnid' => $columnid,
'position' => $position,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'columnid' => $columnid,
'position' => $position,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::reorder_report_column_sorting($reportid, $columnid, $position);
$exporter = new custom_report_columns_sorting_exporter(null, [
'report' => $report,
]);
return (array) $exporter->export($PAGE->get_renderer('core'));
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return custom_report_columns_sorting_exporter::get_read_structure();
}
}
+99
View File
@@ -0,0 +1,99 @@
<?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\external\columns\sort;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\report;
use core_reportbuilder\external\custom_report_columns_sorting_exporter;
/**
* External method for toggling report column sorting
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class toggle extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'columnid' => new external_value(PARAM_INT, 'Column ID'),
'enabled' => new external_value(PARAM_BOOL, 'Sort enabled'),
'direction' => new external_value(PARAM_INT, 'Sort direction', VALUE_DEFAULT, SORT_ASC),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $columnid
* @param bool $enabled
* @param int $direction
* @return array
*/
public static function execute(int $reportid, int $columnid, bool $enabled, int $direction = SORT_ASC): array {
global $PAGE;
[
'reportid' => $reportid,
'columnid' => $columnid,
'enabled' => $enabled,
'direction' => $direction,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'columnid' => $columnid,
'enabled' => $enabled,
'direction' => $direction,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::toggle_report_column_sorting($reportid, $columnid, $enabled, $direction);
$exporter = new custom_report_columns_sorting_exporter(null, [
'report' => $report,
]);
return (array) $exporter->export($PAGE->get_renderer('core'));
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return custom_report_columns_sorting_exporter::get_read_structure();
}
}
+97
View File
@@ -0,0 +1,97 @@
<?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\external\conditions;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\external\custom_report_conditions_exporter;
use core_reportbuilder\local\helpers\report;
/**
* External method for adding report conditions
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'uniqueidentifier' => new external_value(PARAM_RAW, 'Unique identifier of the condition'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param string $uniqueidentifier
* @return array
*/
public static function execute(int $reportid, string $uniqueidentifier): array {
global $PAGE, $OUTPUT;
[
'reportid' => $reportid,
'uniqueidentifier' => $uniqueidentifier,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'uniqueidentifier' => $uniqueidentifier,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::add_report_condition($reportid, $uniqueidentifier);
// Set current URL and force bootstrap_renderer to initiate moodle page.
$PAGE->set_url('/');
$OUTPUT->header();
$PAGE->start_collecting_javascript_requirements();
$exporter = new custom_report_conditions_exporter(null, ['report' => $report]);
$export = $exporter->export($PAGE->get_renderer('core'));
$export->javascript = $PAGE->requires->get_end_code();
return (array) $export;
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_single_structure {
return custom_report_conditions_exporter::get_read_structure();
}
}
+97
View File
@@ -0,0 +1,97 @@
<?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\external\conditions;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\external\custom_report_conditions_exporter;
use core_reportbuilder\local\helpers\report;
/**
* External method for deleting report conditions
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'conditionid' => new external_value(PARAM_INT, 'Condition ID'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $conditionid
* @return array
*/
public static function execute(int $reportid, int $conditionid): array {
global $PAGE, $OUTPUT;
[
'reportid' => $reportid,
'conditionid' => $conditionid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'conditionid' => $conditionid,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::delete_report_condition($reportid, $conditionid);
// Set current URL and force bootstrap_renderer to initiate moodle page.
$PAGE->set_url('/');
$OUTPUT->header();
$PAGE->start_collecting_javascript_requirements();
$exporter = new custom_report_conditions_exporter(null, ['report' => $report]);
$export = $exporter->export($PAGE->get_renderer('core'));
$export->javascript = $PAGE->requires->get_end_code();
return (array) $export;
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_single_structure {
return custom_report_conditions_exporter::get_read_structure();
}
}
+101
View File
@@ -0,0 +1,101 @@
<?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\external\conditions;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\external\custom_report_conditions_exporter;
use core_reportbuilder\local\helpers\report;
/**
* External method for re-ordering report conditions
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reorder extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'conditionid' => new external_value(PARAM_INT, 'Condition ID'),
'position' => new external_value(PARAM_INT, 'New condition position')
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $conditionid
* @param int $position
* @return array
*/
public static function execute(int $reportid, int $conditionid, int $position): array {
global $PAGE, $OUTPUT;
[
'reportid' => $reportid,
'conditionid' => $conditionid,
'position' => $position,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'conditionid' => $conditionid,
'position' => $position,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::reorder_report_condition($reportid, $conditionid, $position);
// Set current URL and force bootstrap_renderer to initiate moodle page.
$PAGE->set_url('/');
$OUTPUT->header();
$PAGE->start_collecting_javascript_requirements();
$exporter = new custom_report_conditions_exporter(null, ['report' => $report]);
$export = $exporter->export($PAGE->get_renderer('core'));
$export->javascript = $PAGE->requires->get_end_code();
return (array) $export;
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_single_structure {
return custom_report_conditions_exporter::get_read_structure();
}
}
+92
View File
@@ -0,0 +1,92 @@
<?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\external\conditions;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\external\custom_report_conditions_exporter;
/**
* External method for resetting report conditions
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reset extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @return array
*/
public static function execute(int $reportid): array {
global $PAGE, $OUTPUT;
[
'reportid' => $reportid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
$report->set_condition_values([]);
// Set current URL and force bootstrap_renderer to initiate moodle page.
$PAGE->set_url('/');
$OUTPUT->header();
$PAGE->start_collecting_javascript_requirements();
$exporter = new custom_report_conditions_exporter(null, ['report' => $report]);
$export = $exporter->export($PAGE->get_renderer('core'));
$export->javascript = $PAGE->requires->get_end_code();
return (array) $export;
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_single_structure {
return custom_report_conditions_exporter::get_read_structure();
}
}
@@ -0,0 +1,85 @@
<?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\external;
use core_collator;
use core_component;
use renderer_base;
use core_reportbuilder\local\audiences\base;
/**
* Custom report audience cards exporter class
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_audience_cards_exporter extends custom_report_menu_cards_exporter {
/**
* Get the additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
$menucards = [];
// Iterate over all audience types.
$audiences = core_component::get_component_classes_in_namespace(null, 'reportbuilder\\audience');
$audiencekeyindex = 0;
foreach ($audiences as $class => $path) {
if (is_subclass_of($class, base::class)) {
$audience = $class::instance();
if (!$audience->user_can_add()) {
continue;
}
// New menu card per component.
$componentname = $audience->get_component_displayname();
if (!array_key_exists($componentname, $menucards)) {
$menucards[$componentname] = [
'name' => $componentname,
'key' => 'index' . ++$audiencekeyindex,
'items' => [],
];
}
// Append menu card item per audience.
$menucards[$componentname]['items'][] = [
'name' => $audience->get_name(),
'identifier' => get_class($audience),
'title' => get_string('addaudience', 'core_reportbuilder', $audience->get_name()),
'action' => 'add-audience',
'disabled' => !$audience->is_available(),
];
}
}
// Order items in each menu card alphabetically.
array_walk($menucards, static function(array &$menucard): void {
core_collator::asort_array_of_arrays_by_key($menucard['items'], 'name');
$menucard['items'] = array_values($menucard['items']);
});
return [
'menucards' => array_values($menucards),
];
}
}
@@ -0,0 +1,88 @@
<?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\external;
use renderer_base;
use core\external\exporter;
use core_reportbuilder\datasource;
use core_reportbuilder\form\card_view;
/**
* Custom report card view exporter class
*
* @package core_reportbuilder
* @copyright 2021 Mikel Martín <mikel@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_card_view_exporter extends exporter {
/**
* Return a list of objects that are related to the exporter
*
* @return array
*/
protected static function define_related(): array {
return [
'report' => datasource::class,
];
}
/**
* Return the list of additional properties for read structure and export
*
* @return array[]
*/
protected static function define_other_properties(): array {
return [
'form' => [
'type' => PARAM_RAW,
],
'helpicon' => [
'type' => PARAM_RAW,
],
];
}
/**
* Get the additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
/** @var datasource $report */
$report = $this->related['report'];
$reportid = $report->get_report_persistent()->get('id');
$reportsettings = $report->get_settings_values();
$cardviewsettings = [
'showfirsttitle' => $reportsettings['cardview_showfirsttitle'] ?? 0,
'visiblecolumns' => $reportsettings['cardview_visiblecolumns'] ?? 1,
];
$cardviewform = new card_view(null, null, 'post', '', [], true,
array_merge(['reportid' => $reportid], $cardviewsettings));
$cardviewform->set_data_for_dynamic_submission();
return [
'form' => $cardviewform->render(),
'helpicon' => $output->help_icon('cardview', 'core_reportbuilder'),
];
}
}
@@ -0,0 +1,84 @@
<?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\external;
use renderer_base;
use core_reportbuilder\datasource;
/**
* Custom report column cards exporter class
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_column_cards_exporter extends custom_report_menu_cards_exporter {
/**
* Return a list of objects that are related to the exporter
*
* @return array
*/
protected static function define_related(): array {
return [
'report' => datasource::class,
];
}
/**
* Get the additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
/** @var datasource $report */
$report = $this->related['report'];
$menucards = [];
foreach ($report->get_columns() as $column) {
if ($column->get_is_deprecated()) {
continue;
}
// New menu card per entity.
$entityname = $column->get_entity_name();
if (!array_key_exists($entityname, $menucards)) {
$menucards[$entityname] = [
'name' => (string) $report->get_entity_title($entityname),
'key' => $entityname,
'items' => [],
];
}
// Append menu card item per column.
$menucards[$entityname]['items'][] = [
'name' => $column->get_title(),
'identifier' => $column->get_unique_identifier(),
'title' => get_string('addcolumn', 'core_reportbuilder', $column->get_title()),
'action' => 'report-add-column',
];
}
return [
'menucards' => array_values($menucards),
];
}
}
@@ -0,0 +1,133 @@
<?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\external;
use core_collator;
use pix_icon;
use renderer_base;
use core\external\exporter;
use core_reportbuilder\datasource;
use core_reportbuilder\local\report\column;
/**
* Custom report columns sorting exporter class
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_columns_sorting_exporter extends exporter {
/**
* Return a list of objects that are related to the exporter
*
* @return array
*/
protected static function define_related(): array {
return [
'report' => datasource::class,
];
}
/**
* Return the list of additional properties for read structure and export
*
* @return array[]
*/
protected static function define_other_properties(): array {
return [
'hassortablecolumns' => [
'type' => PARAM_BOOL,
],
'sortablecolumns' => [
'type' => [
'id' => ['type' => PARAM_INT],
'title' => ['type' => PARAM_TEXT],
'heading' => ['type' => PARAM_TEXT],
'sortdirection' => ['type' => PARAM_INT],
'sortenabled' => ['type' => PARAM_BOOL],
'sortorder' => ['type' => PARAM_INT],
'sorticon' => [
'type' => [
'key' => ['type' => PARAM_RAW],
'component' => ['type' => PARAM_COMPONENT],
'title' => ['type' => PARAM_NOTAGS],
],
],
'movetitle' => ['type' => PARAM_TEXT],
'sortenabledtitle' => ['type' => PARAM_TEXT],
],
'multiple' => true,
],
'helpicon' => [
'type' => PARAM_RAW,
],
];
}
/**
* Get the additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
/** @var datasource $report */
$report = $this->related['report'];
// Filter/retrieve all "sortable" active columns.
$sortablecolumns = array_filter($report->get_active_columns(), function(column $column): bool {
return $column->get_is_sortable();
});
$sortablecolumns = array_map(function(column $column) use ($report): array {
$persistent = $column->get_persistent();
$columntitle = $column->get_title();
$columnheading = $persistent->get_formatted_heading($report->get_context());
$columnsortascending = ($persistent->get('sortdirection') == SORT_ASC);
$sortenabledtitle = $persistent->get('sortenabled') ? 'columnsortdisable' : 'columnsortenable';
$sortdirectiontitle = $columnsortascending ? 'columnsortdirectiondesc' : 'columnsortdirectionasc';
$icon = $columnsortascending ? 't/uplong' : 't/downlong';
$sorticon = new pix_icon($icon, get_string($sortdirectiontitle, 'core_reportbuilder', $columntitle));
return [
'id' => $persistent->get('id'),
'title' => $columntitle,
'heading' => $columnheading !== '' ? $columnheading : $columntitle,
'sortdirection' => $persistent->get('sortdirection'),
'sortenabled' => $persistent->get('sortenabled'),
'sortorder' => $persistent->get('sortorder'),
'sorticon' => $sorticon->export_for_pix(),
'movetitle' => get_string('movesorting', 'core_reportbuilder', $columntitle),
'sortenabledtitle' => get_string($sortenabledtitle, 'core_reportbuilder', $columntitle),
];
}, $sortablecolumns);
core_collator::asort_array_of_arrays_by_key($sortablecolumns, 'sortorder', core_collator::SORT_NUMERIC);
return [
'hassortablecolumns' => !empty($sortablecolumns),
'sortablecolumns' => array_values($sortablecolumns),
'helpicon' => $output->help_icon('sorting', 'core_reportbuilder'),
];
}
}
@@ -0,0 +1,149 @@
<?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\external;
use renderer_base;
use core\external\exporter;
use core_reportbuilder\datasource;
use core_reportbuilder\form\condition;
use core_reportbuilder\local\report\filter;
/**
* Custom report conditions exporter class
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_conditions_exporter extends exporter {
/**
* Return a list of objects that are related to the exporter
*
* @return array
*/
protected static function define_related(): array {
return [
'report' => datasource::class,
];
}
/**
* Return the list of additional properties for read structure and export
*
* @return array[]
*/
protected static function define_other_properties(): array {
return [
'hasavailableconditions' => [
'type' => PARAM_BOOL,
],
'availableconditions' => [
'type' => [
'optiongroup' => [
'type' => [
'text' => ['type' => PARAM_TEXT],
'values' => [
'type' => [
'value' => ['type' => PARAM_TEXT],
'visiblename' => ['type' => PARAM_TEXT],
],
'multiple' => true,
],
],
],
],
'multiple' => true,
],
'hasactiveconditions' => [
'type' => PARAM_BOOL,
],
'activeconditionsform' => [
'type' => PARAM_RAW,
],
'helpicon' => [
'type' => PARAM_RAW,
],
'javascript' => [
'type' => PARAM_RAW,
'optional' => true,
],
];
}
/**
* Get the additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
/** @var datasource $report */
$report = $this->related['report'];
// Current condition instances contained in the report.
$conditions = $report->get_active_conditions();
$conditionidentifiers = array_map(static function(filter $condition): string {
return $condition->get_unique_identifier();
}, $conditions);
$availableconditions = [];
// Populate available conditions.
foreach ($report->get_conditions() as $condition) {
// Conditions can only be added once per report, skip if it already exists.
if (in_array($condition->get_unique_identifier(), $conditionidentifiers) || $condition->get_is_deprecated()) {
continue;
}
$entityname = $condition->get_entity_name();
if (!array_key_exists($entityname, $availableconditions)) {
$availableconditions[$entityname] = [
'optiongroup' => [
'text' => $report->get_entity_title($entityname)->out(),
'values' => [],
],
];
}
$availableconditions[$entityname]['optiongroup']['values'][] = [
'value' => $condition->get_unique_identifier(),
'visiblename' => $condition->get_header(),
];
}
// Generate conditions form if any present.
$conditionspresent = !empty($conditions);
if ($conditionspresent) {
$conditionsform = new condition(null, null, 'post', '', [], true, [
'reportid' => $report->get_report_persistent()->get('id'),
]);
$conditionsform->set_data_for_dynamic_submission();
}
return [
'hasavailableconditions' => !empty($availableconditions),
'availableconditions' => array_values($availableconditions),
'hasactiveconditions' => $conditionspresent,
'activeconditionsform' => $conditionspresent ? $conditionsform->render() : '',
'helpicon' => $output->help_icon('conditions', 'core_reportbuilder'),
];
}
}
@@ -0,0 +1,107 @@
<?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\external;
use renderer_base;
use core\external\exporter;
use core_reportbuilder\datasource;
use core_reportbuilder\table\custom_report_table_view;
/**
* Custom report data exporter class
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_data_exporter extends exporter {
/**
* Return a list of objects that are related to the exporter
*
* @return array
*/
protected static function define_related(): array {
return [
'report' => datasource::class,
'page' => 'int',
'perpage' => 'int',
];
}
/**
* Return the list of additional properties for read structure and export
*
* @return array[]
*/
protected static function define_other_properties(): array {
return [
'headers' => [
'type' => PARAM_RAW,
'multiple' => true,
],
'rows' => [
'type' => [
'columns' => [
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'multiple' => true,
],
],
'multiple' => true,
],
'totalrowcount' => ['type' => PARAM_INT],
];
}
/**
* Get the additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
global $DB;
/** @var datasource $report */
$report = $this->related['report'];
$table = custom_report_table_view::create($report->get_report_persistent()->get('id'));
$table->setup();
// Internally the current page is zero-based, but this method expects value plus one.
$table->set_page_number($this->related['page'] + 1);
$table->query_db($this->related['perpage'], false);
$tablerows = [];
foreach ($table->rawdata as $record) {
$tablerows[] = [
'columns' => array_values($table->format_row($record)),
];
}
$table->close_recordset();
return [
'headers' => $table->headers,
'rows' => $tablerows,
'totalrowcount' => $DB->count_records_sql($table->countsql, $table->countparams),
];
}
}
@@ -0,0 +1,80 @@
<?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\external;
use core_user;
use renderer_base;
use core\external\persistent_exporter;
use core_reportbuilder\datasource;
use core_reportbuilder\manager;
use core_reportbuilder\local\models\report;
use core_user\external\user_summary_exporter;
/**
* Custom report details exporter class
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_details_exporter extends persistent_exporter {
/** @var report The persistent object we will export. */
protected $persistent = null;
/**
* Return the name of the class we are exporting
*
* @return string
*/
protected static function define_class(): string {
return report::class;
}
/**
* Return a list of additional properties used only for display
*
* @return array
*/
protected static function define_other_properties(): array {
return [
'sourcename' => [
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
],
'modifiedby' => ['type' => user_summary_exporter::read_properties_definition()],
];
}
/**
* Get additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
$source = $this->persistent->get('source');
$usermodified = core_user::get_user($this->persistent->get('usermodified'));
return [
'sourcename' => manager::report_source_exists($source, datasource::class) ? $source::get_name() : null,
'modifiedby' => (new user_summary_exporter($usermodified))->export($output),
];
}
}
@@ -0,0 +1,216 @@
<?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\external;
use renderer_base;
use core\persistent;
use core\external\persistent_exporter;
use core_reportbuilder\manager;
use core_reportbuilder\datasource;
use core_reportbuilder\form\filter as form_filter;
use core_reportbuilder\local\models\report;
use core_reportbuilder\table\custom_report_table;
use core_reportbuilder\table\custom_report_table_filterset;
use core_reportbuilder\table\custom_report_table_view;
use core_reportbuilder\table\custom_report_table_view_filterset;
use core_table\local\filter\integer_filter;
/**
* Custom report exporter class
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_exporter extends persistent_exporter {
/** @var report The persistent object we will export. */
protected $persistent = null;
/** @var bool */
protected $editmode;
/** @var string */
protected $download;
/**
* report_exporter constructor.
*
* @param persistent $persistent
* @param array $related
* @param bool $editmode
* @param string $download
*/
public function __construct(persistent $persistent, array $related = [], bool $editmode = true, string $download = '') {
parent::__construct($persistent, $related);
$this->editmode = $editmode;
$this->download = $download;
}
/**
* Return the name of the class we are exporting
*
* @return string
*/
protected static function define_class(): string {
return report::class;
}
/**
* Return a list of objects that are related to the persistent
*
* @return array
*/
protected static function define_related(): array {
return [
'pagesize' => 'int?',
];
}
/**
* Return a list of additional properties used only for display
*
* @return array
*/
protected static function define_other_properties(): array {
return [
'table' => ['type' => PARAM_RAW],
'filtersapplied' => ['type' => PARAM_INT],
'filterspresent' => ['type' => PARAM_BOOL],
'filtersform' => ['type' => PARAM_RAW],
'attributes' => [
'type' => [
'name' => ['type' => PARAM_TEXT],
'value' => ['type' => PARAM_TEXT]
],
'multiple' => true,
],
'classes' => ['type' => PARAM_TEXT],
'editmode' => ['type' => PARAM_BOOL],
'sidebarmenucards' => [
'type' => custom_report_column_cards_exporter::read_properties_definition(),
'optional' => true,
],
'conditions' => [
'type' => custom_report_conditions_exporter::read_properties_definition(),
'optional' => true,
],
'filters' => [
'type' => custom_report_filters_exporter::read_properties_definition(),
'optional' => true,
],
'sorting' => [
'type' => custom_report_columns_sorting_exporter::read_properties_definition(),
'optional' => true,
],
'cardview' => [
'type' => custom_report_card_view_exporter::read_properties_definition(),
'optional' => true,
],
'javascript' => ['type' => PARAM_RAW],
];
}
/**
* Get additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
/** @var datasource $report */
$report = manager::get_report_from_persistent($this->persistent);
$filterspresent = false;
$filtersform = '';
$attributes = [];
if ($this->editmode) {
$table = custom_report_table::create($this->persistent->get('id'));
$table->set_filterset(new custom_report_table_filterset());
} else {
// We store the pagesize within the table filterset so that it's available between AJAX requests.
$filterset = new custom_report_table_view_filterset();
$filterset->add_filter(new integer_filter('pagesize', null, [$this->related['pagesize']]));
$table = custom_report_table_view::create($this->persistent->get('id'), $this->download);
$table->set_filterset($filterset);
// Generate filters form if report contains any filters.
$filterspresent = !empty($report->get_active_filters());
if ($filterspresent && empty($this->download)) {
$filtersform = $this->generate_filters_form()->render();
}
// Get the report classes and attributes.
$reportattributes = $report->get_attributes();
if (isset($reportattributes['class'])) {
$classes = $reportattributes['class'];
unset($reportattributes['class']);
}
$attributes = array_map(static function($key, $value): array {
return ['name' => $key, 'value' => $value];
}, array_keys($reportattributes), $reportattributes);
}
// If we are editing we need all this information for the template.
$editordata = [];
if ($this->editmode) {
$menucardsexporter = new custom_report_column_cards_exporter(null, ['report' => $report]);
$editordata['sidebarmenucards'] = (array) $menucardsexporter->export($output);
$conditionsexporter = new custom_report_conditions_exporter(null, ['report' => $report]);
$editordata['conditions'] = (array) $conditionsexporter->export($output);
$filtersexporter = new custom_report_filters_exporter(null, ['report' => $report]);
$editordata['filters'] = (array) $filtersexporter->export($output);
$sortingexporter = new custom_report_columns_sorting_exporter(null, ['report' => $report]);
$editordata['sorting'] = (array) $sortingexporter->export($output);
$cardviewexporter = new custom_report_card_view_exporter(null, ['report' => $report]);
$editordata['cardview'] = (array) $cardviewexporter->export($output);
}
return [
'table' => $output->render($table),
'filtersapplied' => $report->get_applied_filter_count(),
'filterspresent' => $filterspresent,
'filtersform' => $filtersform,
'attributes' => $attributes,
'classes' => $classes ?? '',
'editmode' => $this->editmode,
'javascript' => '',
] + $editordata;
}
/**
* Generate filters form for the report
*
* @return form_filter
*/
private function generate_filters_form(): form_filter {
$filtersform = new form_filter(null, null, 'post', '', [], true, [
'reportid' => $this->persistent->get('id'),
'parameters' => json_encode([]),
]);
$filtersform->set_data_for_dynamic_submission();
return $filtersform;
}
}
@@ -0,0 +1,163 @@
<?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\external;
use renderer_base;
use core\external\exporter;
use core_reportbuilder\datasource;
use core_reportbuilder\local\report\filter;
use core_reportbuilder\output\filter_heading_editable;
/**
* Custom report filters exporter class
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class custom_report_filters_exporter extends exporter {
/**
* Return a list of objects that are related to the exporter
*
* @return array
*/
protected static function define_related(): array {
return [
'report' => datasource::class,
];
}
/**
* Return the list of additional properties for read structure and export
*
* @return array[]
*/
protected static function define_other_properties(): array {
return [
'hasavailablefilters' => [
'type' => PARAM_BOOL,
],
'availablefilters' => [
'type' => [
'optiongroup' => [
'type' => [
'text' => ['type' => PARAM_TEXT],
'values' => [
'type' => [
'value' => ['type' => PARAM_TEXT],
'visiblename' => ['type' => PARAM_TEXT],
],
'multiple' => true,
],
],
],
],
'multiple' => true,
],
'hasactivefilters' => [
'type' => PARAM_BOOL,
],
'activefilters' => [
'type' => [
'id' => ['type' => PARAM_INT],
'heading' => ['type' => PARAM_TEXT],
'headingeditable' => ['type' => PARAM_RAW],
'sortorder' => ['type' => PARAM_INT],
'movetitle' => ['type' => PARAM_TEXT],
'entityname' => ['type' => PARAM_TEXT],
],
'multiple' => true,
],
'helpicon' => [
'type' => PARAM_RAW,
],
];
}
/**
* Get the additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
/** @var datasource $report */
$report = $this->related['report'];
// Current filter instances contained in the report.
$filters = $report->get_active_filters();
$filteridentifiers = array_map(static function(filter $filter): string {
return $filter->get_unique_identifier();
}, $filters);
$availablefilters = $activefilters = [];
// Populate available filters.
foreach ($report->get_filters() as $filter) {
// Filters can only be added once per report, skip if it already exists.
if (in_array($filter->get_unique_identifier(), $filteridentifiers) || $filter->get_is_deprecated()) {
continue;
}
$entityname = $filter->get_entity_name();
if (!array_key_exists($entityname, $availablefilters)) {
$availablefilters[$entityname] = [
'optiongroup' => [
'text' => $report->get_entity_title($entityname)->out(),
'values' => [],
],
];
}
$availablefilters[$entityname]['optiongroup']['values'][] = [
'value' => $filter->get_unique_identifier(),
'visiblename' => $filter->get_header(),
];
}
// Populate active filters.
$filterinstances = $report->get_filter_instances();
foreach ($filterinstances as $filterinstance) {
$persistent = $filterinstance->get_filter_persistent();
$entityname = $filterinstance->get_entity_name();
$displayvalue = $filterinstance->get_header();
$editable = new filter_heading_editable(0, $persistent);
$activefilters[] = [
'id' => $persistent->get('id'),
'entityname' => $report->get_entity_title($entityname)->out(),
'heading' => $displayvalue,
'headingeditable' => $editable->render($output),
'sortorder' => $persistent->get('filterorder'),
'movetitle' => get_string('movefilter', 'core_reportbuilder', $displayvalue),
];
}
return [
'hasavailablefilters' => !empty($availablefilters),
'availablefilters' => array_values($availablefilters),
'hasactivefilters' => !empty($activefilters),
'activefilters' => $activefilters,
'helpicon' => $output->help_icon('filters', 'core_reportbuilder'),
];
}
}
@@ -0,0 +1,76 @@
<?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\external;
use core\external\exporter;
/**
* Custom report menu cards exporter abstract class
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class custom_report_menu_cards_exporter extends exporter {
/**
* Return the list of additional properties for read structure and export
*
* @return array[]
*/
protected static function define_other_properties(): array {
return [
'menucards' => [
'type' => [
'name' => [
'type' => PARAM_TEXT,
],
'key' => [
'type' => PARAM_TEXT,
],
'items' => [
'type' => [
'name' => [
'type' => PARAM_TEXT,
],
'identifier' => [
'type' => PARAM_TEXT,
],
'title' => [
'type' => PARAM_TEXT,
],
'action' => [
'type' => PARAM_TEXT,
],
'disabled' => [
'type' => PARAM_BOOL,
'optional' => true,
'default' => false,
],
],
'optional' => true,
'multiple' => true,
],
],
'optional' => true,
'multiple' => true,
],
];
}
}
+91
View File
@@ -0,0 +1,91 @@
<?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\external\filters;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\external\custom_report_filters_exporter;
use core_reportbuilder\local\helpers\report;
/**
* External method for adding report filters
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class add extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'uniqueidentifier' => new external_value(PARAM_RAW, 'Unique identifier of the filter'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param string $uniqueidentifier
* @return array
*/
public static function execute(int $reportid, string $uniqueidentifier): array {
global $PAGE;
[
'reportid' => $reportid,
'uniqueidentifier' => $uniqueidentifier,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'uniqueidentifier' => $uniqueidentifier,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::add_report_filter($reportid, $uniqueidentifier);
$exporter = new custom_report_filters_exporter(null, [
'report' => $report,
]);
return (array) $exporter->export($PAGE->get_renderer('core'));
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return custom_report_filters_exporter::get_read_structure();
}
}
+91
View File
@@ -0,0 +1,91 @@
<?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\external\filters;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\external\custom_report_filters_exporter;
use core_reportbuilder\local\helpers\report;
/**
* External method for deleting report filters
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'filterid' => new external_value(PARAM_INT, 'Filter ID'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $filterid
* @return array
*/
public static function execute(int $reportid, int $filterid): array {
global $PAGE;
[
'reportid' => $reportid,
'filterid' => $filterid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'filterid' => $filterid,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::delete_report_filter($reportid, $filterid);
$exporter = new custom_report_filters_exporter(null, [
'report' => $report,
]);
return (array) $exporter->export($PAGE->get_renderer('core'));
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return custom_report_filters_exporter::get_read_structure();
}
}
+95
View File
@@ -0,0 +1,95 @@
<?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\external\filters;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\external\custom_report_filters_exporter;
use core_reportbuilder\local\helpers\report;
/**
* External method for re-ordering report filters
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reorder extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'filterid' => new external_value(PARAM_INT, 'Filter ID'),
'position' => new external_value(PARAM_INT, 'New filter position')
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $filterid
* @param int $position
* @return array
*/
public static function execute(int $reportid, int $filterid, int $position): array {
global $PAGE;
[
'reportid' => $reportid,
'filterid' => $filterid,
'position' => $position,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'filterid' => $filterid,
'position' => $position,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
report::reorder_report_filter($reportid, $filterid, $position);
$exporter = new custom_report_filters_exporter(null, [
'report' => $report,
]);
return (array) $exporter->export($PAGE->get_renderer('core'));
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return custom_report_filters_exporter::get_read_structure();
}
}
+86
View File
@@ -0,0 +1,86 @@
<?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\external\filters;
use core_external\external_api;
use core_external\external_value;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\user_filter_manager;
/**
* External method for resetting report filters
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reset extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'parameters' => new external_value(PARAM_RAW, 'JSON encoded report parameters', VALUE_DEFAULT, ''),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param string $parameters JSON encoded parameters used to re-create the report, for instance for those reports that
* require parameters as part of their {@see \core_reportbuilder\system_report::can_view} implementation
* @return bool
*/
public static function execute(int $reportid, string $parameters = ''): bool {
[
'reportid' => $reportid,
'parameters' => $parameters,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'parameters' => $parameters,
]);
$report = manager::get_report_from_id($reportid, (array) json_decode($parameters));
self::validate_context($report->get_context());
// System report permission is implicitly handled, we need to make sure custom report can be viewed.
$persistent = $report->get_report_persistent();
if ($persistent->get('type') === $report::TYPE_CUSTOM_REPORT) {
permission::require_can_view_report($persistent);
}
return user_filter_manager::reset_all($reportid);
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL, 'Success');
}
}
+88
View File
@@ -0,0 +1,88 @@
<?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\external\filters;
use core_external\external_api;
use core_external\external_value;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
/**
* External method for setting report filter values
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class set extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'parameters' => new external_value(PARAM_RAW, 'JSON encoded report parameters', VALUE_DEFAULT, ''),
'values' => new external_value(PARAM_RAW, 'JSON encoded filter values'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param string $parameters
* @param string $values
* @return bool
*/
public static function execute(int $reportid, string $parameters, string $values): bool {
[
'reportid' => $reportid,
'parameters' => $parameters,
'values' => $values,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'parameters' => $parameters,
'values' => $values,
]);
$report = manager::get_report_from_id($reportid, (array) json_decode($parameters));
self::validate_context($report->get_context());
// System report permission is implicitly handled, we need to make sure custom report can be viewed.
$persistent = $report->get_report_persistent();
if ($persistent->get('type') === $report::TYPE_CUSTOM_REPORT) {
permission::require_can_view_report($persistent);
}
return $report->set_filter_values((array) json_decode($values));
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL, 'Success');
}
}
+79
View File
@@ -0,0 +1,79 @@
<?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\external\reports;
use core_external\external_api;
use core_external\external_value;
use core_external\external_function_parameters;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\report;
use core_reportbuilder\local\models\report as report_model;
/**
* External method for deleting reports
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @return bool
*/
public static function execute(int $reportid): bool {
[
'reportid' => $reportid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
]);
// Load the report model for deletion. Note we don't use the manager class because it validates the report source,
// and we want user to be able to delete report, even if it's no longer associated with a valid source.
$report = new report_model($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report);
return report::delete_report($reportid);
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL, 'Success');
}
}
+106
View File
@@ -0,0 +1,106 @@
<?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\external\reports;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\output\custom_report;
use core_reportbuilder\external\custom_report_exporter;
use moodle_url;
/**
* External method for getting a custom report
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'editmode' => new external_value(PARAM_BOOL, 'Whether editing mode is enabled', VALUE_DEFAULT, 0),
'pagesize' => new external_value(PARAM_INT, 'Page size', VALUE_DEFAULT, 0),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param bool $editmode
* @param int $pagesize
* @return array
*/
public static function execute(int $reportid, bool $editmode, int $pagesize = 0): array {
global $PAGE, $OUTPUT;
[
'reportid' => $reportid,
'editmode' => $editmode,
'pagesize' => $pagesize,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'editmode' => $editmode,
'pagesize' => $pagesize,
]);
$report = manager::get_report_from_id($reportid);
if ($pagesize > 0) {
$report->set_default_per_page($pagesize);
}
self::validate_context($report->get_context());
if ($editmode) {
permission::require_can_edit_report($report->get_report_persistent());
} else {
permission::require_can_view_report($report->get_report_persistent());
}
// Set current URL and force bootstrap_renderer to initiate moodle page.
$PAGE->set_url(new moodle_url('/'));
$OUTPUT->header();
$PAGE->start_collecting_javascript_requirements();
$renderer = $PAGE->get_renderer('core_reportbuilder');
$context = (new custom_report($report->get_report_persistent(), $editmode))->export_for_template($renderer);
$context->javascript = $PAGE->requires->get_end_code();
return (array)$context;
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return custom_report_exporter::get_read_structure();
}
}
+108
View File
@@ -0,0 +1,108 @@
<?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\external\reports;
use context_system;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_multiple_structure;
use core_external\external_function_parameters;
use core_external\external_warnings;
use stdClass;
use core_reportbuilder\permission;
use core_reportbuilder\external\custom_report_details_exporter;
use core_reportbuilder\local\helpers\audience;
use core_reportbuilder\local\models\report;
/**
* External method for listing users' custom reports
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class listing extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'page' => new external_value(PARAM_INT, 'Page number', VALUE_DEFAULT, 0),
'perpage' => new external_value(PARAM_INT, 'Reports per page', VALUE_DEFAULT, 10),
]);
}
/**
* External method execution
*
* @param int $page
* @param int $perpage
* @return array
*/
public static function execute(int $page = 0, int $perpage = 10): array {
global $DB, $PAGE;
[
'page' => $page,
'perpage' => $perpage,
] = self::validate_parameters(self::execute_parameters(), [
'page' => $page,
'perpage' => $perpage,
]);
$context = context_system::instance();
self::validate_context($context);
permission::require_can_view_reports_list(null, $context);
// Filter list of reports by those the user can access.
[$where, $params] = audience::user_reports_list_access_sql('r');
$reports = $DB->get_records_sql("
SELECT r.*
FROM {" . report::TABLE . "} r
WHERE r.type = 0 AND {$where}
ORDER BY r.name, r.id", $params, $page * $perpage, $perpage);
$output = $PAGE->get_renderer('core');
return [
'reports' => array_map(static function(stdClass $report) use ($output): array {
$exporter = new custom_report_details_exporter(new report(0, $report));
return (array) $exporter->export($output);
}, $reports),
'warnings' => [],
];
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'reports' => new external_multiple_structure(custom_report_details_exporter::get_read_structure()),
'warnings' => new external_warnings(),
]);
}
}
+102
View File
@@ -0,0 +1,102 @@
<?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\external\reports;
use core_reportbuilder\manager;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_external\external_warnings;
use core_reportbuilder\permission;
use core_reportbuilder\external\{custom_report_data_exporter, custom_report_details_exporter};
/**
* External method for retrieving custom report content
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class retrieve extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'page' => new external_value(PARAM_INT, 'Page number', VALUE_DEFAULT, 0),
'perpage' => new external_value(PARAM_INT, 'Reports per page', VALUE_DEFAULT, 10),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $page
* @param int $perpage
* @return array
*/
public static function execute(int $reportid, int $page = 0, int $perpage = 10): array {
global $PAGE;
[
'reportid' => $reportid,
'page' => $page,
'perpage' => $perpage,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'page' => $page,
'perpage' => $perpage,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
$persistent = $report->get_report_persistent();
permission::require_can_view_report($persistent);
$output = $PAGE->get_renderer('core');
return [
'details' => (array) (new custom_report_details_exporter($persistent))->export($output),
'data' => (array) (new custom_report_data_exporter(null, [
'report' => $report, 'page' => $page, 'perpage' => $perpage,
]))->export($output),
'warnings' => [],
];
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'details' => custom_report_details_exporter::get_read_structure(),
'data' => custom_report_data_exporter::get_read_structure(),
'warnings' => new external_warnings(),
]);
}
}
+89
View File
@@ -0,0 +1,89 @@
<?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\external\reports;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_external\external_warnings;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\event\report_viewed;
/**
* External method to record the viewing of a report
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class view extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @return array
*/
public static function execute(int $reportid): array {
[
'reportid' => $reportid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
$persistent = $report->get_report_persistent();
permission::require_can_view_report($persistent);
// Trigger the report viewed event.
report_viewed::create_from_object($persistent)->trigger();
return [
'status' => true,
'warnings' => [],
];
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'Success'),
'warnings' => new external_warnings(),
]);
}
}
+81
View File
@@ -0,0 +1,81 @@
<?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\external\schedules;
use core_external\external_api;
use core_external\external_value;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\schedule;
/**
* External method for deleting report schedules
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'scheduleid' => new external_value(PARAM_INT, 'Schedule ID'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $scheduleid
* @return bool
*/
public static function execute(int $reportid, int $scheduleid): bool {
[
'reportid' => $reportid,
'scheduleid' => $scheduleid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'scheduleid' => $scheduleid,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
return schedule::delete_schedule($reportid, $scheduleid);
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL);
}
}
+87
View File
@@ -0,0 +1,87 @@
<?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\external\schedules;
use core_external\external_api;
use core_external\external_value;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\task\send_schedule;
/**
* External method for sending report schedules
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'scheduleid' => new external_value(PARAM_INT, 'Schedule ID'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $scheduleid
* @return bool
*/
public static function execute(int $reportid, int $scheduleid): bool {
[
'reportid' => $reportid,
'scheduleid' => $scheduleid,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'scheduleid' => $scheduleid,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
$sendschedule = new send_schedule();
$sendschedule->set_custom_data([
'reportid' => $reportid,
'scheduleid' => $scheduleid,
]);
return (bool) \core\task\manager::queue_adhoc_task($sendschedule);
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL);
}
}
+85
View File
@@ -0,0 +1,85 @@
<?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\external\schedules;
use core_external\external_api;
use core_external\external_value;
use core_external\external_function_parameters;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\schedule;
/**
* External method for toggling report schedules
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class toggle extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'reportid' => new external_value(PARAM_INT, 'Report ID'),
'scheduleid' => new external_value(PARAM_INT, 'Schedule ID'),
'enabled' => new external_value(PARAM_BOOL, 'Schedule enabled'),
]);
}
/**
* External method execution
*
* @param int $reportid
* @param int $scheduleid
* @param bool $enabled
* @return bool
*/
public static function execute(int $reportid, int $scheduleid, bool $enabled): bool {
[
'reportid' => $reportid,
'scheduleid' => $scheduleid,
'enabled' => $enabled,
] = self::validate_parameters(self::execute_parameters(), [
'reportid' => $reportid,
'scheduleid' => $scheduleid,
'enabled' => $enabled,
]);
$report = manager::get_report_from_id($reportid);
self::validate_context($report->get_context());
permission::require_can_edit_report($report->get_report_persistent());
return schedule::toggle_schedule($reportid, $scheduleid, $enabled);
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL);
}
}
@@ -0,0 +1,115 @@
<?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\external;
use renderer_base;
use core\external\exporter;
use core_reportbuilder\system_report;
use core_reportbuilder\table\system_report_table;
/**
* System report data exporter class
*
* @package core_reportbuilder
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class system_report_data_exporter extends exporter {
/**
* Return a list of objects that are related to the exporter
*
* @return array
*/
protected static function define_related(): array {
return [
'report' => system_report::class,
'page' => 'int',
'perpage' => 'int',
];
}
/**
* Return the list of additional properties for read structure and export
*
* @return array[]
*/
protected static function define_other_properties(): array {
return [
'headers' => [
'type' => PARAM_RAW,
'multiple' => true,
],
'rows' => [
'type' => [
'columns' => [
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'multiple' => true,
],
],
'multiple' => true,
],
'totalrowcount' => ['type' => PARAM_INT],
];
}
/**
* Get the additional values to inject while exporting
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
global $DB;
/** @var system_report $report */
$report = $this->related['report'];
$table = system_report_table::create($report->get_report_persistent()->get('id'), $report->get_parameters());
$table->guess_base_url();
$table->setup();
// Internally the current page is zero-based, but this method expects value plus one.
$table->set_page_number($this->related['page'] + 1);
$table->query_db($this->related['perpage'], false);
// Ensure we only return defined columns, excluding those such as "select all" and "actions".
$columnsbyalias = $report->get_active_columns_by_alias();
$tableheaders = array_combine(array_flip($table->columns), $table->headers);
$tableheaders = array_intersect_key($tableheaders, $columnsbyalias);
$tablerows = [];
foreach ($table->rawdata as $record) {
$columns = array_intersect_key($table->format_row($record), $columnsbyalias);
$tablerows[] = [
'columns' => array_values($columns),
];
}
$table->close_recordset();
return [
'headers' => array_values($tableheaders),
'rows' => $tablerows,
'totalrowcount' => $DB->count_records_sql($table->countsql, $table->countparams),
];
}
}
@@ -0,0 +1,141 @@
<?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\external;
use core\external\persistent_exporter;
use core_table\local\filter\integer_filter;
use core_table\local\filter\string_filter;
use core_reportbuilder\system_report;
use core_reportbuilder\form\filter;
use core_reportbuilder\local\models\report;
use core_reportbuilder\table\system_report_table;
use core_reportbuilder\table\system_report_table_filterset;
use renderer_base;
/**
* Report exporter class
*
* @package core_reportbuilder
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class system_report_exporter extends persistent_exporter {
/**
* Return the name of the class we are exporting
*
* @return string
*/
protected static function define_class(): string {
return report::class;
}
/**
* Return a list of objects that are related to the persistent
*
* @return array
*/
protected static function define_related(): array {
return [
'source' => system_report::class,
'parameters' => 'string',
];
}
/**
* Return a list of additional properties used only for display
*
* @return array
*/
protected static function define_other_properties(): array {
return [
'table' => ['type' => PARAM_RAW],
'parameters' => ['type' => PARAM_RAW],
'filterspresent' => ['type' => PARAM_BOOL],
'filtersapplied' => ['type' => PARAM_INT],
'filtersform' => ['type' => PARAM_RAW],
'attributes' => [
'type' => [
'name' => ['type' => PARAM_TEXT],
'value' => ['type' => PARAM_TEXT]
],
'multiple' => true,
],
'classes' => ['type' => PARAM_TEXT],
];
}
/**
* Get additional values to inject while exporting
*
* @uses \core_reportbuilder\output\renderer::render_system_report_table()
*
* @param renderer_base $output
* @return array
*/
protected function get_other_values(renderer_base $output): array {
/** @var system_report $source */
$source = $this->related['source'];
/** @var string $parameters */
$parameters = $this->related['parameters'];
/** @var int $reportid */
$reportid = $this->persistent->get('id');
// We store the report ID and parameters within the table filterset so that they are available between AJAX requests.
$filterset = new system_report_table_filterset();
$filterset->add_filter(new integer_filter('reportid', null, [$reportid]));
$filterset->add_filter(new string_filter('parameters', null, [$parameters]));
$params = (array) json_decode($parameters, true);
$table = system_report_table::create($reportid, $params);
$table->set_filterset($filterset);
// Generate filters form if report uses the default form, and contains any filters.
$filterspresent = $source->get_filter_form_default() && !empty($source->get_active_filters());
if ($filterspresent && empty($params['download'])) {
$filtersform = new filter(null, null, 'post', '', [], true, [
'reportid' => $reportid,
'parameters' => $parameters,
]);
$filtersform->set_data_for_dynamic_submission();
}
// Get the report classes and attributes.
$sourceattributes = $source->get_attributes();
if (isset($sourceattributes['class'])) {
$classes = $sourceattributes['class'];
unset($sourceattributes['class']);
}
$attributes = array_map(static function($key, $value): array {
return ['name' => $key, 'value' => $value];
}, array_keys($sourceattributes), $sourceattributes);
return [
'table' => $output->render($table),
'parameters' => $parameters,
'filterspresent' => $filterspresent,
'filtersapplied' => $source->get_applied_filter_count(),
'filtersform' => $filterspresent ? $filtersform->render() : '',
'attributes' => $attributes,
'classes' => $classes ?? '',
];
}
}
@@ -0,0 +1,120 @@
<?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\external\systemreports;
use core_external\external_api;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_external\external_value;
use core_reportbuilder\report_access_exception;
use core_reportbuilder\system_report_factory;
/**
* External method for validating access to a system report
*
* @package core_reportbuilder
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class can_view extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'source' => new external_value(PARAM_RAW, 'Report class path'),
'context' => self::get_context_parameters(),
'component' => new external_value(PARAM_COMPONENT, 'Report component', VALUE_DEFAULT, ''),
'area' => new external_value(PARAM_AREA, 'Report area', VALUE_DEFAULT, ''),
'itemid' => new external_value(PARAM_INT, 'Report item ID', VALUE_DEFAULT, 0),
'parameters' => new external_multiple_structure(
new external_single_structure([
'name' => new external_value(PARAM_RAW),
'value' => new external_value(PARAM_RAW),
]),
'Report parameters', VALUE_DEFAULT, []
),
]);
}
/**
* External method execution
*
* @param string $source
* @param array $context
* @param string $component
* @param string $area
* @param int $itemid
* @param array[] $parameters
* @return bool
*/
public static function execute(
string $source,
array $context,
string $component = '',
string $area = '',
int $itemid = 0,
array $parameters = [],
): bool {
[
'source' => $source,
'context' => $context,
'component' => $component,
'area' => $area,
'itemid' => $itemid,
'parameters' => $parameters,
] = self::validate_parameters(self::execute_parameters(), [
'source' => $source,
'context' => $context,
'component' => $component,
'area' => $area,
'itemid' => $itemid,
'parameters' => $parameters,
]);
$context = self::get_context_from_params($context);
self::validate_context($context);
// Flatten the report parameters.
$parameters = array_combine(array_column($parameters, 'name'), array_column($parameters, 'value'));
try {
$report = system_report_factory::create($source, $context, $component, $area, $itemid, $parameters);
$report->require_can_view();
} catch (report_access_exception $exception) {
return false;
}
return true;
}
/**
* External method return value
*
* @return external_value
*/
public static function execute_returns(): external_value {
return new external_value(PARAM_BOOL);
}
}
@@ -0,0 +1,138 @@
<?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\external\systemreports;
use core_external\external_api;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_function_parameters;
use core_external\external_value;
use core_external\external_warnings;
use core_reportbuilder\system_report_factory;
use core_reportbuilder\external\system_report_data_exporter;
/**
* External method for retrieving system report content
*
* @package core_reportbuilder
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class retrieve extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'source' => new external_value(PARAM_RAW, 'Report class path'),
'context' => self::get_context_parameters(),
'component' => new external_value(PARAM_COMPONENT, 'Report component', VALUE_DEFAULT, ''),
'area' => new external_value(PARAM_AREA, 'Report area', VALUE_DEFAULT, ''),
'itemid' => new external_value(PARAM_INT, 'Report item ID', VALUE_DEFAULT, 0),
'parameters' => new external_multiple_structure(
new external_single_structure([
'name' => new external_value(PARAM_RAW),
'value' => new external_value(PARAM_RAW),
]),
'Report parameters', VALUE_DEFAULT, []
),
'page' => new external_value(PARAM_INT, 'Page number', VALUE_DEFAULT, 0),
'perpage' => new external_value(PARAM_INT, 'Reports per page', VALUE_DEFAULT, 10),
]);
}
/**
* External method execution
*
* @param string $source
* @param array $context
* @param string $component
* @param string $area
* @param int $itemid
* @param array[] $parameters
* @param int $page
* @param int $perpage
* @return array[]
*/
public static function execute(
string $source,
array $context,
string $component = '',
string $area = '',
int $itemid = 0,
array $parameters = [],
int $page = 0,
int $perpage = 10,
): array {
global $PAGE;
[
'source' => $source,
'context' => $context,
'component' => $component,
'area' => $area,
'itemid' => $itemid,
'parameters' => $parameters,
'page' => $page,
'perpage' => $perpage,
] = self::validate_parameters(self::execute_parameters(), [
'source' => $source,
'context' => $context,
'component' => $component,
'area' => $area,
'itemid' => $itemid,
'parameters' => $parameters,
'page' => $page,
'perpage' => $perpage,
]);
$context = self::get_context_from_params($context);
self::validate_context($context);
// Flatten the report parameters.
$parameters = array_combine(array_column($parameters, 'name'), array_column($parameters, 'value'));
$report = system_report_factory::create($source, $context, $component, $area, $itemid, $parameters);
$report->require_can_view();
$output = $PAGE->get_renderer('core');
return [
'data' => (array) (new system_report_data_exporter(null, [
'report' => $report, 'page' => $page, 'perpage' => $perpage,
]))->export($output),
'warnings' => [],
];
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'data' => system_report_data_exporter::get_read_structure(),
'warnings' => new external_warnings(),
]);
}
}
+176
View File
@@ -0,0 +1,176 @@
<?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\form;
use context;
use core_form\dynamic_form;
use core_reportbuilder\local\audiences\base;
use core_reportbuilder\output\audience_heading_editable;
use core_reportbuilder\permission;
use moodle_url;
use stdClass;
/**
* Dynamic audience form
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class audience extends dynamic_form {
/**
* Audience we work with
*
* @return base
*/
protected function get_audience(): base {
$id = $this->optional_param('id', 0, PARAM_INT);
$record = new stdClass();
if (!$id) {
// New instance, pre-define report id and classname.
$record->reportid = $this->optional_param('reportid', null, PARAM_INT);
$record->classname = $this->optional_param('classname', null, PARAM_RAW_TRIMMED);
}
return base::instance($id, $record);
}
/**
* Form definition.
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'reportid');
$mform->setType('reportid', PARAM_INT);
$mform->addElement('hidden', 'classname');
$mform->setType('classname', PARAM_RAW_TRIMMED);
// Embed form defined in audience class.
$audience = $this->get_audience();
$audience->get_config_form($mform);
$this->add_action_buttons();
}
/**
* Form validation.
*
* @param array $data array of ("fieldname"=>value) of submitted data
* @param array $files array of uploaded files "element_name"=>tmp_file_path
* @return array of "element_name"=>"error_description" if there are errors,
* or an empty array if everything is OK (true allowed for backwards compatibility too).
*/
public function validation($data, $files) {
$audience = $this->get_audience();
return $audience->validate_config_form($data);
}
/**
* Returns context where this form is used
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
return $this->get_audience()->get_persistent()->get_report()->get_context();
}
/**
* Ensure current user is able to use this form
*
* A {@see \core_reportbuilder\report_access_exception} will be thrown if they can't
*/
protected function check_access_for_dynamic_submission(): void {
$audience = $this->get_audience();
$report = $audience->get_persistent()->get_report();
permission::require_can_edit_report($report);
// Check whether we are able to add/edit the current audience.
$audience->get_persistent()->get('id') === 0
? $audience->require_user_can_add()
: $audience->require_user_can_edit();
}
/**
* Process the form submission, used if form was submitted via AJAX
*/
public function process_dynamic_submission() {
global $PAGE;
$formdata = $this->get_data();
$audience = $this->get_audience();
$configdata = $audience::retrieve_configdata($formdata);
if (!$formdata->id) {
// New audience.
$audience = $audience::create($formdata->reportid, $configdata);
} else {
// Editing audience.
$audience->update_configdata($configdata);
}
$persistent = $audience->get_persistent();
$editable = new audience_heading_editable(0, $persistent);
return [
'instanceid' => $persistent->get('id'),
'heading' => $editable->render($PAGE->get_renderer('core')),
'description' => $audience->get_description(),
];
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
$audience = $this->get_audience();
$persistent = $audience->get_persistent();
// Populate form data based on whether we are editing/creating an audience.
if ($persistent->get('id') !== 0) {
$formdata = [
'id' => $persistent->get('id'),
'reportid' => $persistent->get('reportid'),
'classname' => $persistent->get('classname'),
] + $audience->get_configdata();
} else {
$formdata = [
'reportid' => $this->optional_param('reportid', null, PARAM_INT),
'classname' => $this->optional_param('classname', null, PARAM_RAW_TRIMMED),
];
}
$this->set_data($formdata);
}
/**
* Page url
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->optional_param('reportid', 0, PARAM_INT)]);
}
}
+130
View File
@@ -0,0 +1,130 @@
<?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\form;
use context;
use moodle_url;
use core_form\dynamic_form;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\report\base;
use core_reportbuilder\local\models\report;
/**
* Card view dynamic form
*
* @package core_reportbuilder
* @copyright 2021 Mikel Martín <mikel@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class card_view extends dynamic_form {
/**
* Return instance of the report using the card view form
*
* @return base
*/
private function get_report(): base {
$report = new report($this->optional_param('reportid', 0, PARAM_INT));
$parameters = (array) json_decode($this->optional_param('parameters', '', PARAM_RAW));
return manager::get_report_from_persistent($report, $parameters);
}
/**
* Returns context where this form is used
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
return $this->get_report()->get_context();
}
/**
* Check if current user has access to this form, otherwise throw exception
*/
public function check_access_for_dynamic_submission(): void {
permission::require_can_edit_report($this->get_report()->get_report_persistent());
}
/**
* Store the conditions values and operators
*
* @return bool
*/
public function process_dynamic_submission(): bool {
$values = $this->get_data();
$settings = [
'cardview_showfirsttitle' => (int)$values->showfirsttitle,
// Minimum value for 'cardview_visiblecolumns' should be 1.
'cardview_visiblecolumns' => max((int)$values->visiblecolumns, 1)
];
return $this->get_report()->set_settings_values($settings);
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
$report = $this->get_report();
$settings = $report->get_settings_values();
$defaults = [
// Maximum value for 'cardview_visiblecolumns' should be the report total number of columns.
'visiblecolumns' => min($settings['cardview_visiblecolumns'] ?? 1, count($report->get_active_columns())),
'showfirsttitle' => $settings['cardview_showfirsttitle'] ?? 0,
];
$this->set_data(array_merge($defaults, $this->_ajaxformdata));
}
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
return new moodle_url('/reportbuilder/edit.php');
}
/**
* Card view form definition
*/
public function definition(): void {
$report = $this->get_report();
$mform = $this->_form;
$mform->addElement('hidden', 'reportid');
$mform->setType('reportid', PARAM_INT);
// Generate select options from 1 to report total number of columns.
$visiblecolumns = range(1, max(count($report->get_active_columns()), 1));
$mform->addElement('select', 'visiblecolumns', get_string('cardviewvisiblecolumns', 'core_reportbuilder'),
array_combine($visiblecolumns, $visiblecolumns));
$mform->setType('visiblecolumns', PARAM_INT);
$mform->addElement('selectyesno', 'showfirsttitle', get_string('cardviewfirstcolumntitle', 'core_reportbuilder'));
$mform->setType('showfirsttitle', PARAM_BOOL);
$mform->disable_form_change_checker();
$this->add_action_buttons(false);
}
}
+157
View File
@@ -0,0 +1,157 @@
<?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\form;
use context;
use moodle_url;
use core_form\dynamic_form;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\report\base;
use core_reportbuilder\local\models\report;
/**
* Dynamic condition form
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class condition extends dynamic_form {
/**
* Return instance of the report using the condition form
*
* @return base
*/
private function get_report(): base {
$report = new report($this->optional_param('reportid', 0, PARAM_INT));
$parameters = (array) json_decode($this->optional_param('parameters', '', PARAM_RAW));
return manager::get_report_from_persistent($report, $parameters);
}
/**
* Return the context for the form, it should be that of the system report itself
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
return $this->get_report()->get_context();
}
/**
* Ensure current user is able to use this form
*
* A {@see \core_reportbuilder\report_access_exception} will be thrown if they can't
*/
protected function check_access_for_dynamic_submission(): void {
permission::require_can_edit_report($this->get_report()->get_report_persistent());
}
/**
* Process the form submission
*
* @return bool
*/
public function process_dynamic_submission() {
$values = $this->get_data();
// Remove some unneeded fields.
unset($values->reportid, $values->parameters);
return $this->get_report()->set_condition_values((array) $values);
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
$defaults = [
'reportid' => $this->optional_param('reportid', 0, PARAM_INT),
'parameters' => $this->optional_param('parameters', 0, PARAM_RAW),
];
$this->set_data(array_merge($defaults, $this->get_report()->get_condition_values()));
}
/**
* URL of the page using this form
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
return new moodle_url('/');
}
/**
* Condition form definition
*/
protected function definition() {
global $OUTPUT;
$mform = $this->_form;
$mform->addElement('hidden', 'reportid');
$mform->setType('reportid', PARAM_INT);
$mform->addElement('hidden', 'parameters');
$mform->setType('parameters', PARAM_RAW);
// Wrap the form elements inside an outer container, as drag/drop requires draggable elements to be immediate
// descendants of said container. Note this is identified by it's data-region property in the editor module.
$mform->addElement('html', '<div class="list-group mt-2" data-region="active-conditions">');
// Allow each condition instance to add itself to this form, wrapping each inside custom header/footer template.
$conditioninstances = $this->get_report()->get_condition_instances();
foreach ($conditioninstances as $conditioninstance) {
$persistent = $conditioninstance->get_filter_persistent();
$entityname = $conditioninstance->get_entity_name();
$displayvalue = $conditioninstance->get_header();
$mform->addElement('html', $OUTPUT->render_from_template('core_reportbuilder/local/conditions/header', [
'id' => $persistent->get('id'),
'entityname' => $this->get_report()->get_entity_title($entityname),
'heading' => $displayvalue,
'sortorder' => $persistent->get('filterorder'),
'movetitle' => get_string('movecondition', 'core_reportbuilder', $displayvalue),
]));
$conditioninstance->setup_form($mform);
$mform->addElement('html', $OUTPUT->render_from_template('core_reportbuilder/local/conditions/footer', []));
}
$mform->addElement('html', '</div>');
$this->set_display_vertical();
// We'll add a second submit button to the form that will be used to reset current report conditions.
$mform->registerNoSubmitButton('resetconditions');
$buttons = [];
$buttons[] = $mform->createElement('submit', 'submitbutton', get_string('apply', 'core_reportbuilder'));
$buttons[] = $mform->createElement('submit', 'resetconditions', get_string('resetall', 'core_reportbuilder'),
null, null, ['customclassoverride' => 'btn-link ml-1']);
$mform->addGroup($buttons, 'buttonar', get_string('formactions', 'core_form'), '', false)
->setHiddenLabel(true);
$mform->closeHeaderBefore('buttonar');
$mform->disable_form_change_checker();
}
}
+160
View File
@@ -0,0 +1,160 @@
<?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\form;
use context;
use core_reportbuilder\local\report\base;
use core_reportbuilder\permission;
use moodle_url;
use core_form\dynamic_form;
use core_reportbuilder\manager;
use core_reportbuilder\local\models\report;
use core_reportbuilder\local\models\filter as filter_model;
/**
* Dynamic filter form
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filter extends dynamic_form {
/**
* Return instance of the system report using the filter form
*
* @return base
*/
private function get_report(): base {
$reportpersistent = new report($this->optional_param('reportid', 0, PARAM_INT));
$parameters = (array) json_decode($this->optional_param('parameters', '', PARAM_RAW));
return manager::get_report_from_persistent($reportpersistent, $parameters);
}
/**
* Return the context for the form, it should be that of the system report itself
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
return ($this->get_report())->get_context();
}
/**
* Ensure current user is able to use this form
*
* A {@see \core_reportbuilder\report_access_exception} will be thrown if they can't
*/
protected function check_access_for_dynamic_submission(): void {
$reportpersistent = $this->get_report()->get_report_persistent();
if ($reportpersistent->get('type') === base::TYPE_CUSTOM_REPORT) {
permission::require_can_view_report($reportpersistent);
} else {
$this->get_report()->require_can_view();
}
}
/**
* Process the form submission
*
* @return int Number of applied filter instances
*/
public function process_dynamic_submission() {
$values = $this->get_data();
// Remove some unneeded fields, apply filters.
unset($values->reportid, $values->parameters);
$this->get_report()->set_filter_values((array) $values);
return $this->get_report()->get_applied_filter_count();
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
$defaults = [
'reportid' => $this->optional_param('reportid', 0, PARAM_INT),
'parameters' => $this->optional_param('parameters', 0, PARAM_RAW),
];
$this->set_data(array_merge($defaults, $this->get_report()->get_filter_values()));
}
/**
* URL of the page using this form
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
return new moodle_url('/');
}
/**
* Filter form definition. It should provide necessary field itself, then allow all report filters to add their own elements
*/
protected function definition() {
global $OUTPUT;
$mform = $this->_form;
$mform->addElement('hidden', 'reportid');
$mform->setType('reportid', PARAM_INT);
$mform->addElement('hidden', 'parameters');
$mform->setType('parameters', PARAM_RAW);
// Allow each filter instance to add itself to this form, wrapping each inside custom header/footer template.
$filterinstances = $this->get_report()->get_filter_instances();
foreach ($filterinstances as $filterinstance) {
$header = $filterinstance->get_header();
// Check if filter has a custom header set.
if ($persistent = $filterinstance->get_filter_persistent()) {
if ('' !== (string) $persistent->get('heading')) {
$header = $persistent->get_formatted_heading($this->get_report()->get_context());
}
}
$mform->addElement('html', $OUTPUT->render_from_template('core_reportbuilder/local/filters/header', [
'name' => $header,
]));
$filterinstance->setup_form($mform);
$mform->addElement('html', $OUTPUT->render_from_template('core_reportbuilder/local/filters/footer', []));
}
$this->set_display_vertical();
// We'll add a second submit button to the form that will be used to reset current report filters.
$mform->registerNoSubmitButton('resetfilters');
$buttons = [];
$buttons[] = $mform->createElement('submit', 'submitbutton', get_string('apply', 'core_reportbuilder'));
$buttons[] = $mform->createElement('submit', 'resetfilters', get_string('resetall', 'core_reportbuilder'),
null, null, ['customclassoverride' => 'btn-link ml-1']);
$mform->addGroup($buttons, 'buttonar', get_string('formactions', 'core_form'), '', false)
->setHiddenLabel(true);
$mform->closeHeaderBefore('buttonar');
$mform->disable_form_change_checker();
}
}
+176
View File
@@ -0,0 +1,176 @@
<?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\form;
use context;
use context_system;
use core_reportbuilder\permission;
use moodle_url;
use core_form\dynamic_form;
use core_reportbuilder\datasource;
use core_reportbuilder\manager;
use core_reportbuilder\local\helpers\report as reporthelper;
use core_tag_tag;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("$CFG->libdir/formslib.php");
/**
* Report details form
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report extends dynamic_form {
/**
* Return instance of the custom report we are editing, or null when creating a new report
*
* @return datasource|null
*/
protected function get_custom_report(): ?datasource {
if ($reportid = $this->optional_param('id', 0, PARAM_INT)) {
/** @var datasource $customreport */
$customreport = manager::get_report_from_id($reportid);
return $customreport;
}
return null;
}
/**
* Return the context for the form, it should be that of the custom report itself, or system when creating a new report
*
* @return context
*/
public function get_context_for_dynamic_submission(): context {
if ($report = $this->get_custom_report()) {
return $report->get_context();
} else {
return context_system::instance();
}
}
/**
* Ensure current user is able to use this form
*
* A {@see \core_reportbuilder\report_access_exception} will be thrown if they can't
*/
protected function check_access_for_dynamic_submission(): void {
$report = $this->get_custom_report();
if ($report) {
permission::require_can_edit_report($report->get_report_persistent());
} else {
permission::require_can_create_report();
}
}
/**
* Form definition
*/
public function definition() {
$mform = $this->_form;
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('text', 'name', get_string('name'));
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', null, 'required', null, 'client');
$mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255);
// Allow user to select report source if creating a new report.
if (!$this->get_custom_report()) {
$default = ['' => ['' => get_string('selectareportsource', 'core_reportbuilder')]];
$mform->addElement('selectgroups', 'source', get_string('reportsource', 'core_reportbuilder'),
array_merge($default, manager::get_report_datasources()));
$mform->addRule('source', null, 'required', null, 'client');
$mform->addHelpButton('source', 'reportsource', 'core_reportbuilder');
$mform->addElement('advcheckbox', 'includedefaultsetup', get_string('includedefaultsetup', 'core_reportbuilder'));
$mform->setDefault('includedefaultsetup', 1);
$mform->addHelpButton('includedefaultsetup', 'includedefaultsetup', 'core_reportbuilder');
}
$mform->addElement('advcheckbox', 'uniquerows', get_string('uniquerows', 'core_reportbuilder'));
$mform->addHelpButton('uniquerows', 'uniquerows', 'core_reportbuilder');
$mform->addElement('tags', 'tags', get_string('tags'), [
'component' => 'core_reportbuilder', 'itemtype' => 'reportbuilder_report',
]);
}
/**
* Process the form submission
*
* @return string The URL to advance to upon completion
*/
public function process_dynamic_submission() {
$data = $this->get_data();
if ($data->id) {
$reportpersistent = reporthelper::update_report($data);
} else {
$reportpersistent = reporthelper::create_report($data, (bool)$data->includedefaultsetup);
}
return (new moodle_url('/reportbuilder/edit.php', ['id' => $reportpersistent->get('id')]))->out(false);
}
/**
* Load in existing data as form defaults
*/
public function set_data_for_dynamic_submission(): void {
if ($persistent = $this->get_custom_report()?->get_report_persistent()) {
$tags = core_tag_tag::get_item_tags_array('core_reportbuilder', 'reportbuilder_report', $persistent->get('id'));
$this->set_data(array_merge((array) $persistent->to_record(), ['tags' => $tags]));
}
}
/**
* URL of the page using this form
*
* @return moodle_url
*/
public function get_page_url_for_dynamic_submission(): moodle_url {
return new moodle_url('/reportbuilder/index.php');
}
/**
* Perform some extra moodle validation
*
* @param array $data
* @param array $files
* @return array
*/
public function validation($data, $files): array {
$errors = [];
if (trim($data['name']) === '') {
$errors['name'] = get_string('required');
}
return $errors;
}
}
+259
View File
@@ -0,0 +1,259 @@
<?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\form;
use context;
use core_user;
use html_writer;
use moodle_url;
use core\output\notification;
use core_form\dynamic_form;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\audience;
use core_reportbuilder\local\helpers\schedule as helper;
use core_reportbuilder\local\models\schedule as model;
use core_reportbuilder\local\report\base;
/**
* Schedule form
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class schedule extends dynamic_form {
/**
* Return instance of the system report using the filter form
*
* @return base
*/
private function get_report(): base {
$reportid = $this->optional_param('reportid', 0, PARAM_INT);
return manager::get_report_from_id($reportid);
}
/**
* Return the context for the form, it should be that of the report itself
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
return $this->get_report()->get_context();
}
/**
* Ensure current user is able to use this form
*
* A {@see \core_reportbuilder\report_access_exception} will be thrown if they can't
*/
protected function check_access_for_dynamic_submission(): void {
$persistent = $this->get_report()->get_report_persistent();
permission::require_can_edit_report($persistent);
}
/**
* Form definition
*/
protected function definition() {
global $OUTPUT;
$mform = $this->_form;
$mform->addElement('hidden', 'reportid');
$mform->setType('reportid', PARAM_INT);
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
// General fields.
$mform->addElement('header', 'headergeneral', get_string('general'));
$mform->addElement('text', 'name', get_string('name'));
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name', null, 'required', null, 'client');
$mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255);
$mform->addElement('select', 'format', get_string('format'), helper::get_format_options());
$mform->setType('format', PARAM_PLUGIN);
$mform->addElement('date_time_selector', 'timescheduled', get_string('startingfrom'), ['optional' => false]);
$mform->setType('timescheduled', PARAM_INT);
$mform->addElement('select', 'recurrence', get_string('recurrence', 'core_reportbuilder'),
helper::get_recurrence_options());
$mform->setType('recurrence', PARAM_INT);
// View report data as.
$context = $this->get_context_for_dynamic_submission();
if (has_capability('moodle/reportbuilder:scheduleviewas', $context)) {
$mform->addElement('select', 'userviewas', get_string('scheduleviewas', 'core_reportbuilder'),
helper::get_viewas_options());
$mform->setType('userviewas', PARAM_INT);
$options = [
'ajax' => 'core_user/form_user_selector',
'multiple' => false,
'valuehtmlcallback' => function($userid) use ($context): string {
$user = core_user::get_user($userid);
return fullname($user, has_capability('moodle/site:viewfullnames', $context));
}
];
$mform->addElement('autocomplete', 'user', get_string('user'), [], $options)->setHiddenLabel(true);
$mform->hideIf('user', 'userviewas', 'neq', model::REPORT_VIEWAS_USER);
}
// Audience fields.
$mform->addElement('header', 'headeraudience', get_string('audience', 'core_reportbuilder'));
$mform->setExpanded('headeraudience', true);
$audiences = audience::get_base_records($this->optional_param('reportid', 0, PARAM_INT));
if (empty($audiences)) {
$notification = new notification(get_string('noaudiences', 'core_reportbuilder'), notification::NOTIFY_INFO, false);
$mform->addElement('static', 'noaudiences', '', $OUTPUT->render($notification));
}
$audiencecheckboxes = [];
foreach ($audiences as $audience) {
$persistent = $audience->get_persistent();
// Check for a custom name, otherwise fall back to default.
if ('' === $audiencelabel = $persistent->get_formatted_heading($context)) {
$audiencelabel = get_string('audiencelabel', 'core_reportbuilder', (object) [
'name' => $audience->get_name(),
'description' => $audience->get_description(),
]);
}
$audiencecheckboxes[] = $mform->createElement('checkbox', $persistent->get('id'), $audiencelabel);
}
$mform->addElement('group', 'audiences', '', $audiencecheckboxes, html_writer::div('', 'w-100 mb-2'));
// Message fields.
$mform->addElement('header', 'headermessage', get_string('messagecontent', 'core_reportbuilder'));
$mform->addElement('text', 'subject', get_string('messagesubject', 'core_reportbuilder'));
$mform->setType('subject', PARAM_TEXT);
$mform->addRule('subject', null, 'required', null, 'client');
$mform->addRule('subject', get_string('maximumchars', '', 255), 'maxlength', 255);
$mform->addElement('editor', 'message', get_string('messagebody', 'core_reportbuilder'), null, ['autosave' => false]);
$mform->setType('message', PARAM_RAW);
$mform->addRule('message', null, 'required', null, 'client');
// Advanced.
$mform->addElement('header', 'headeradvanced', get_string('advanced'));
$mform->addElement('select', 'reportempty', get_string('scheduleempty', 'core_reportbuilder'),
helper::get_report_empty_options());
$mform->setType('reportempty', PARAM_INT);
}
/**
* Load form data if we are editing an existing schedule
*/
public function set_data_for_dynamic_submission(): void {
$reportid = $this->optional_param('reportid', 0, PARAM_INT);
$scheduleid = $this->optional_param('id', 0, PARAM_INT);
if ($scheduleid > 0) {
$schedule = model::get_record(['id' => $scheduleid, 'reportid' => $reportid]);
$data = (array) $schedule->to_record();
// Pre-process some of the form fields.
if (!in_array($data['userviewas'], [model::REPORT_VIEWAS_CREATOR, model::REPORT_VIEWAS_RECIPIENT])) {
$data['user'] = $data['userviewas'];
$data['userviewas'] = model::REPORT_VIEWAS_USER;
}
$audiences = json_decode($data['audiences']);
$data['audiences'] = array_fill_keys($audiences, 1);
$data['message'] = [
'text' => $data['message'],
'format' => $data['messageformat'],
];
$this->set_data($data);
} else {
$this->set_data(['reportid' => $reportid]);
}
}
/**
* Form validation
*
* @param array $data
* @param array $files
* @return array
*/
public function validation($data, $files): array {
$errors = parent::validation($data, $files);
if (trim($data['name']) === '') {
$errors['name'] = get_string('required');
}
// Make sure specific user was selected, if required.
if (array_key_exists('userviewas', $data) &&
(int) $data['userviewas'] === model::REPORT_VIEWAS_USER && empty($data['user'])) {
$errors['user'] = get_string('required');
}
if (empty($data['audiences'])) {
$errors['audiences'] = get_string('required');
}
return $errors;
}
/**
* Process form submission
*/
public function process_dynamic_submission(): void {
$data = $this->get_data();
// Pre-process some of the form fields.
if (property_exists($data, 'userviewas') && (int) $data->userviewas === model::REPORT_VIEWAS_USER) {
$data->userviewas = (int) $data->user;
}
$data->audiences = json_encode(array_keys($data->audiences));
['text' => $data->message, 'format' => $data->messageformat] = $data->message;
if ($data->id) {
helper::update_schedule($data);
} else {
helper::create_schedule($data);
}
}
/**
* URL of the page using this form
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
return new moodle_url('/reportbuilder/edit.php', ['id' => $this->optional_param('reportid', 0, PARAM_INT)], 'schedules');
}
}
@@ -0,0 +1,88 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column average aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class avg extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationavg', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all numeric columns
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_INTEGER,
column::TYPE_FLOAT,
column::TYPE_BOOLEAN,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "AVG(1.0 * {$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* For boolean columns we return the average of the values (0..1), numeric columns execute original callbacks if present
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @param int $columntype
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks, int $columntype) {
if (reset($values) === null) {
return null;
}
if ($columntype === column::TYPE_BOOLEAN || empty($callbacks)) {
return format_float((float) reset($values), 1);
}
return parent::format_value($value, $values, $callbacks, $columntype);
}
}
@@ -0,0 +1,141 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Base class for column aggregation types
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base {
/**
* Return the class name of the aggregation type
*
* @return string
*/
final public static function get_class_name(): string {
$namespacedclass = explode('\\', get_called_class());
return end($namespacedclass);
}
/**
* Return the display name of the aggregation
*
* @return lang_string
*/
abstract public static function get_name(): lang_string;
/**
* Whether the aggregation is compatible with the given column type
*
* @param int $columntype The type as defined by the {@see column::set_type} method
* @return bool
*/
abstract public static function compatible(int $columntype): bool;
/**
* Whether the aggregation is sortable, by default return the sortable status of the column itself
*
* @param bool $columnsortable
* @return bool
*/
public static function sortable(bool $columnsortable): bool {
return $columnsortable;
}
/**
* Return SQL suitable for using within {@see get_field_sql} for column fields, by default just the first one
*
* @param string[] $sqlfields
* @return string
*/
public static function get_column_field_sql(array $sqlfields): string {
return reset($sqlfields);
}
/**
* Helper method for concatenating given fields for a column, so they are suitable for aggregation
*
* @param string[] $sqlfields
* @param string $delimeter
* @param string $coalescechar
* @return string
*/
final protected static function get_column_fields_concat(
array $sqlfields,
string $delimeter = ',',
string $coalescechar = ' '
): string {
global $DB;
// We need to ensure all values are char.
$sqlfieldrequirescast = in_array($DB->get_dbfamily(), ['mssql', 'oracle', 'postgres']);
$concatfields = [];
foreach ($sqlfields as $sqlfield) {
if ($sqlfieldrequirescast) {
$sqlfield = $DB->sql_cast_to_char($sqlfield);
}
// Coalesce all the SQL fields. Ensure cross-DB compatibility, and that we always get string data back.
$concatfields[] = "COALESCE({$sqlfield}, '{$coalescechar}')";
$concatfields[] = "'{$delimeter}'";
}
// Slice off the last delimeter.
return $DB->sql_concat(...array_slice($concatfields, 0, -1));
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
abstract public static function get_field_sql(string $field, int $columntype): string;
/**
* Return formatted value for column when applying aggregation, by default executing all callbacks on the value
*
* Should be overridden in child classes that need to format the column value differently (e.g. 'sum' would just show
* a numeric count value)
*
* @param mixed $value
* @param array $values
* @param array $callbacks Array of column callbacks, {@see column::add_callback} for definition
* @param int $columntype The original type of the column, to ensure it is preserved for callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks, int $columntype) {
foreach ($callbacks as $callback) {
[$callable, $arguments] = $callback;
$value = ($callable)($value, (object) $values, $arguments, static::get_class_name());
}
return $value;
}
}
@@ -0,0 +1,81 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column count aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class count extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationcount', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all column types
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return true;
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
global $DB;
if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') {
$field = $DB->sql_compare_text($field, 255);
}
return "COUNT({$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @param int $columntype
* @return int
*/
public static function format_value($value, array $values, array $callbacks, int $columntype): int {
return (int) reset($values);
}
}
@@ -0,0 +1,95 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column count distinct aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class countdistinct extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationcountdistinct', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all column types
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return true;
}
/**
* Override base method to ensure all SQL fields are concatenated together if there are multiple
*
* @param array $sqlfields
* @return string
*/
public static function get_column_field_sql(array $sqlfields): string {
if (count($sqlfields) === 1) {
return parent::get_column_field_sql($sqlfields);
}
return self::get_column_fields_concat($sqlfields);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
global $DB;
if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') {
$field = $DB->sql_compare_text($field, 255);
}
return "COUNT(DISTINCT {$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @param int $columntype
* @return int
*/
public static function format_value($value, array $values, array $callbacks, int $columntype): int {
return (int) reset($values);
}
}
@@ -0,0 +1,139 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\report\column;
/**
* Column group concatenation aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupconcat extends base {
/** @var string Character to use as a delimeter between column fields */
protected const COLUMN_FIELD_DELIMETER = '<|>';
/** @var string Character to use a null coalesce value */
protected const COLUMN_NULL_COALESCE = '<^>';
/** @var string Character to use as a delimeter between field values */
protected const FIELD_VALUE_DELIMETER = '<,>';
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationgroupconcat', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all non-timestamp columns
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return !in_array($columntype, [
column::TYPE_TIMESTAMP,
]);
}
/**
* Override base method to ensure all SQL fields are concatenated together if there are multiple
*
* @param array $sqlfields
* @return string
*/
public static function get_column_field_sql(array $sqlfields): string {
if (count($sqlfields) === 1) {
return parent::get_column_field_sql($sqlfields);
}
return self::get_column_fields_concat($sqlfields, self::COLUMN_FIELD_DELIMETER, self::COLUMN_NULL_COALESCE);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
global $DB;
$fieldsort = database::sql_group_concat_sort($field);
return $DB->sql_group_concat($field, self::FIELD_VALUE_DELIMETER, $fieldsort);
}
/**
* Return formatted value for column when applying aggregation, note we need to split apart the concatenated string
* and apply callbacks to each concatenated value separately
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @param int $columntype
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks, int $columntype) {
$firstvalue = reset($values);
if ($firstvalue === null) {
return '';
}
$formattedvalues = [];
// Store original names of all values that would be present without aggregation.
$valuenames = array_keys($values);
$valuenamescount = count($valuenames);
// Loop over each extracted value from the concatenated string.
$values = explode(self::FIELD_VALUE_DELIMETER, (string)$firstvalue);
foreach ($values as $value) {
// Ensure we have equal number of value names/data, account for truncation by DB.
$valuedata = explode(self::COLUMN_FIELD_DELIMETER, $value);
if ($valuenamescount !== count($valuedata)) {
continue;
}
// Re-construct original values, also ensuring any nulls contained within are restored.
$originalvalues = array_map(static function(string $value): ?string {
return $value === self::COLUMN_NULL_COALESCE ? null : $value;
}, array_combine($valuenames, $valuedata));
$originalvalue = column::get_default_value($originalvalues, $columntype);
// Once we've re-constructed each value, we can apply callbacks to it.
$formattedvalues[] = parent::format_value($originalvalue, $originalvalues, $callbacks, $columntype);
}
$listseparator = get_string('listsep', 'langconfig') . ' ';
return implode($listseparator, $formattedvalues);
}
}
@@ -0,0 +1,85 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\helpers\database;
/**
* Column group concatenation distinct aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupconcatdistinct extends groupconcat {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationgroupconcatdistinct', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all non-timestamp columns in MySQL, Postgres and Oracle only
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
global $DB;
$dbsupportedtype = in_array($DB->get_dbfamily(), [
'mysql',
'postgres',
'oracle',
]);
return $dbsupportedtype && parent::compatible($columntype);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
global $DB;
$fieldsort = database::sql_group_concat_sort($field);
// Postgres handles group concatenation differently in that it requires the expression to be cast to char, so we can't
// simply pass "DISTINCT {$field}" to the {@see \moodle_database::sql_group_concat} method in all cases.
if ($DB->get_dbfamily() === 'postgres') {
$field = $DB->sql_cast_to_char($field);
if ($fieldsort !== '') {
$fieldsort = "ORDER BY {$fieldsort}";
}
return "STRING_AGG(DISTINCT {$field}, '" . self::FIELD_VALUE_DELIMETER . "' {$fieldsort})";
} else {
return $DB->sql_group_concat("DISTINCT {$field}", self::FIELD_VALUE_DELIMETER, $fieldsort);
}
}
}
@@ -0,0 +1,67 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column max aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class max extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationmax', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all numeric/date/boolean types
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_INTEGER,
column::TYPE_FLOAT,
column::TYPE_TIMESTAMP,
column::TYPE_BOOLEAN,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "MAX({$field})";
}
}
@@ -0,0 +1,67 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column min aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class min extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationmin', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all numeric/date/boolean types
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_INTEGER,
column::TYPE_FLOAT,
column::TYPE_TIMESTAMP,
column::TYPE_BOOLEAN,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "MIN({$field})";
}
}
@@ -0,0 +1,81 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
/**
* Column percent aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class percent extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationpercent', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all boolean columns
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_BOOLEAN,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "AVG(1.0 * {$field}) * 100.0";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @param int $columntype
* @return string
*/
public static function format_value($value, array $values, array $callbacks, int $columntype): string {
if (reset($values) === null) {
return '';
}
return format::percent((float) reset($values));
}
}
@@ -0,0 +1,90 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column sum aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sum extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationsum', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all numeric and boolean columns
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_INTEGER,
column::TYPE_FLOAT,
column::TYPE_BOOLEAN,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "SUM({$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* For boolean columns we return the sum of the true values, numeric columns execute original callbacks if present
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @param int $columntype
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks, int $columntype) {
$firstvalue = reset($values);
if ($firstvalue === null) {
return null;
}
if ($columntype === column::TYPE_BOOLEAN || empty($callbacks)) {
$decimalpoints = (int) ($columntype === column::TYPE_FLOAT);
return format_float((float) $firstvalue, $decimalpoints);
}
return parent::format_value($value, $values, $callbacks, $columntype);
}
}
@@ -0,0 +1,289 @@
<?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\audiences;
use core_plugin_manager;
use MoodleQuickForm;
use stdClass;
use core\output\notification;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\models\audience;
use core_reportbuilder\report_access_exception;
/**
* Audience base class
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base {
/** @var int Maximim number of multi-select elements to show in description, before appending "plus X more" */
private const MULTI_SELECT_LIMIT = 5;
/** @var audience The persistent object associated with this audience */
protected $audience;
/**
* Protected constructor, please use the static instance method.
*/
protected function __construct() {
}
/**
* Loads an existing instance of audience with persistent
*
* @param int $id
* @param null|stdClass $record
* @return self|null
*/
final public static function instance(int $id = 0, ?stdClass $record = null): ?self {
$persistent = new audience($id, $record);
// Needed for get_audience_types() method.
if (!$classname = $persistent->get('classname')) {
// Use the called class name.
$classname = get_called_class();
$persistent->set('classname', $classname);
}
// Check if audience type class still exists in the system.
if (!class_exists($classname)) {
return null;
}
$instance = new $classname();
$instance->audience = $persistent;
return $instance;
}
/**
* Creates a new audience and saves it to database
*
* @param int $reportid
* @param array $configdata
* @return self
*/
final public static function create(int $reportid, array $configdata): self {
$record = new stdClass();
$record->reportid = $reportid;
$record->classname = get_called_class();
$record->configdata = json_encode($configdata);
$instance = self::instance(0, $record);
$instance->audience->save();
return $instance;
}
/**
* Return display name of the component the audience belongs to
*
* @return string
*/
final public function get_component_displayname(): string {
[$component] = explode('\\', get_class($this));
if ($plugininfo = core_plugin_manager::instance()->get_plugin_info($component)) {
return $plugininfo->displayname;
}
// Return generic site text for those audiences belonging to core subsystems.
return get_string('site');
}
/**
* Helps to build SQL to retrieve users that matches the current audience
*
* Implementations must use {@see database::generate_alias} and {@see database::generate_param_name} for table/column
* aliases and parameter names
*
* @param string $usertablealias
* @return array array of three elements [$join, $where, $params]
*/
abstract public function get_sql(string $usertablealias): array;
/**
* Returns string for audience category.
*
* @return string
*
* @deprecated since Moodle 4.2 - please do not use this function any more, {@see get_component_displayname}
*/
final public function get_category(): string {
debugging('The function ' . __FUNCTION__ . '() is deprecated, please do not use it any more. ' .
'See class \'get_component_displayname\' method for replacement', DEBUG_DEVELOPER);
return $this->get_component_displayname();
}
/**
* If the current user is able to add this audience type
*
* @return bool
*/
abstract public function user_can_add(): bool;
/**
* If the current user is able to edit this audience type
*
* @return bool
*/
abstract public function user_can_edit(): bool;
/**
* If the current user is able to use this audience type
*
* This method needs to return true if audience type is available to user for
* reasons other than permission check, which is done in {@see user_can_add}.
* (e.g. user can add cohort audience type only if there is at least one cohort
* they can access).
*
* @return bool
*/
public function is_available(): bool {
return true;
}
/**
* Return user friendly name of the audience type
*
* @return string
*/
abstract public function get_name(): string;
/**
* Return the description of this audience type
*
* @return string
*/
abstract public function get_description(): string;
/**
* Helper to format descriptions for audience types that may contain many selected elements, limiting number show according
* to {@see MULTI_SELECT_LIMIT} constant value
*
* @param array $elements
* @return string
*/
protected function format_description_for_multiselect(array $elements): string {
global $OUTPUT;
// Warn user if there are no elements (because they may no longer exist).
$elementcount = count($elements);
if ($elementcount === 0) {
$notification = new notification(get_string('nothingtodisplay'), notification::NOTIFY_WARNING);
return $OUTPUT->render($notification);
}
$listseparator = get_string('listsep', 'langconfig') . ' ';
if ($elementcount > self::MULTI_SELECT_LIMIT) {
$elements = array_slice($elements, 0, self::MULTI_SELECT_LIMIT);
// Append overflow element.
$elementoverflow = $elementcount - self::MULTI_SELECT_LIMIT;
$params = [
'elements' => implode($listseparator, $elements),
'morecount' => $elementoverflow,
];
$description = get_string('audiencemultiselectpostfix', 'core_reportbuilder', $params);
} else {
$description = implode($listseparator, $elements);
}
return $description;
}
/**
* Adds audience-specific form elements
*
* @param MoodleQuickForm $mform The form to add elements to
*/
abstract public function get_config_form(MoodleQuickForm $mform): void;
/**
* Validates the configform of the condition.
*
* @param array $data Data from the form
* @return array Array with errors for each element
*/
public function validate_config_form(array $data): array {
return [];
}
/**
* Returns configdata as an associative array
*
* @return array decoded configdata
*/
final public function get_configdata(): array {
return json_decode($this->audience->get('configdata'), true);
}
/**
* Update configdata in audience persistent
*
* @param array $configdata
*/
final public function update_configdata(array $configdata): void {
$this->audience->set('configdata', json_encode($configdata));
$this->audience->save();
}
/**
* Returns $configdata from form data suitable for use in DB record.
*
* @param stdClass $data data obtained from $mform->get_data()
* @return array $configdata
*/
final public static function retrieve_configdata(stdClass $data): array {
$configdata = (array) $data;
$invalidkeys = array_fill_keys(['id', 'reportid', 'classname'], '');
return array_diff_key($configdata, $invalidkeys);
}
/**
* Return audience persistent.
*
* @return audience
*/
public function get_persistent(): audience {
return $this->audience;
}
/**
* Require current user is able to add this audience type
*
* @throws report_access_exception
*/
final public function require_user_can_add(): void {
if (!$this->user_can_add()) {
throw new report_access_exception('errorreportedit');
}
}
/**
* Require current user is able to edit this audience type
*
* @throws report_access_exception
*/
final public function require_user_can_edit(): void {
if (!$this->user_can_edit()) {
throw new report_access_exception('errorreportedit');
}
}
}
@@ -0,0 +1,417 @@
<?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 coding_exception;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use lang_string;
/**
* Base class for all report entities
*
* @package core_reportbuilder
* @copyright 2019 Marina Glancy <marina@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base {
/** @var string $entityname Internal reference to name of entity */
private $entityname = null;
/** @var lang_string $entitytitle Used as a title for the entity in reports */
private $entitytitle = null;
/** @var array $tablealiases Database tables that this entity uses and their aliases */
private $tablealiases = [];
/** @var array $tablejoinaliases Database tables that have already been joined to the report and their aliases */
private $tablejoinaliases = [];
/** @var string[] $joins List of SQL joins for the entity */
private $joins = [];
/** @var column[] $columns List of columns for the entity */
private $columns = [];
/** @var filter[] $filters List of filters for the entity */
private $filters = [];
/** @var filter[] $conditions List of conditions for the entity */
private $conditions = [];
/**
* Database tables that this entity uses
*
* Must be overridden by the entity to list all database tables that it expects to be present in the main
* SQL or in JOINs added to this entity
*
* @todo in Moodle 4.8 - make abstract when support for {@see get_default_table_aliases} is finally removed
*
* @return string[]
*/
protected function get_default_tables(): array {
static $debuggingshown;
// The default implementation falls back to retrieving deprecated table aliases to determine our table names.
$tablenamealiases = $this->get_default_table_aliases();
if (!empty($tablenamealiases) && !$debuggingshown) {
debugging('The function get_default_table_aliases() is deprecated, please define the entity' .
' tables with get_default_tables() in ' . static::class, DEBUG_DEVELOPER);
// Don't be too spammy with the debugging, this method is called multiple times per entity load.
$debuggingshown = true;
}
return array_keys($tablenamealiases);
}
/**
* Database tables that this entity uses and their default aliases (note that these aliases are now ignored)
*
* @return string[] Array of $tablename => $alias
*
* @deprecated since Moodle 4.4 - aliases are now autogenerated, please implement {@see get_default_tables} instead
*/
protected function get_default_table_aliases(): array {
return [];
}
/**
* The default title for this entity
*
* @return lang_string
*/
abstract protected function get_default_entity_title(): lang_string;
/**
* Initialise the entity, called automatically when it is added to a report
*
* This is where entity defines all its columns and filters by calling:
* - {@see add_column}
* - {@see add_filter}
* - etc
*
* @return self
*/
abstract public function initialise(): self;
/**
* The default machine-readable name for this entity that will be used in the internal names of the columns/filters
*
* @return string
*/
private function get_default_entity_name(): string {
$namespace = explode('\\', get_called_class());
return end($namespace);
}
/**
* Set entity name
*
* @param string $entityname
* @return self
*/
final public function set_entity_name(string $entityname): self {
$this->entityname = $entityname;
return $this;
}
/**
* Return entity name
*
* @return string
*/
final public function get_entity_name(): string {
return $this->entityname ?? $this->get_default_entity_name();
}
/**
* Set entity title
*
* @param lang_string $title
* @return self
*/
final public function set_entity_title(lang_string $title): self {
$this->entitytitle = $title;
return $this;
}
/**
* Get entity title
*
* @return lang_string
*/
final public function get_entity_title(): lang_string {
return $this->entitytitle ?? $this->get_default_entity_title();
}
/**
* Override the default alias for given database table used in entity queries, for instance when the same table is used
* by multiple entities and you want them each to refer to it by the same alias
*
* @param string $tablename One of the tables set by {@see get_default_tables}
* @param string $alias
* @return self
* @throws coding_exception For invalid table name
*/
final public function set_table_alias(string $tablename, string $alias): self {
$tablenames = $this->get_default_tables();
if (!in_array($tablename, $tablenames)) {
throw new coding_exception('Invalid table name', $tablename);
}
$this->tablealiases[$tablename] = $alias;
return $this;
}
/**
* Override multiple default database table aliases used in entity queries as per {@see set_table_alias}
*
* @param array $aliases Array of tablename => alias values
* @return self
*/
final public function set_table_aliases(array $aliases): self {
foreach ($aliases as $tablename => $alias) {
$this->set_table_alias($tablename, $alias);
}
return $this;
}
/**
* Returns an alias used in the queries for a given table
*
* @param string $tablename One of the tables set by {@see get_default_tables}
* @return string
* @throws coding_exception For invalid table name
*/
final public function get_table_alias(string $tablename): string {
$tablenames = $this->get_default_tables();
if (!in_array($tablename, $tablenames)) {
throw new coding_exception('Invalid table name', $tablename);
}
// We don't have the alias yet, generate a new one.
if (!array_key_exists($tablename, $this->tablealiases)) {
$this->set_table_alias($tablename, database::generate_alias());
}
return $this->tablealiases[$tablename];
}
/**
* Returns aliases used in the queries for all tables
*
* @return string[]
*/
final public function get_table_aliases(): array {
$tablenames = $this->get_default_tables();
return array_combine($tablenames, array_map([$this, 'get_table_alias'], $tablenames));
}
/**
* Set the alias for given database table that has already been added to the report. Enables entities to avoid additional
* joins on the same table by allowing re-use of existing table aliases in their own queries, {@see has_table_join_alias}
*
* @param string $tablename
* @param string $alias
* @return self
*/
final public function set_table_join_alias(string $tablename, string $alias): self {
$this->tablejoinaliases[$tablename] = $alias;
// Internally set the same table alias for the entity.
return $this->set_table_alias($tablename, $alias);
}
/**
* Determine whether defined table join alias was specified. Call {@see get_table_alias} to retrieve said value
*
* @param string $tablename
* @return bool
*/
final public function has_table_join_alias(string $tablename): bool {
return array_key_exists($tablename, $this->tablejoinaliases);
}
/**
* Add join clause required for this entity to join to existing tables/entities
*
* @param string $join
* @return self
*/
final public function add_join(string $join): self {
$this->joins[trim($join)] = trim($join);
return $this;
}
/**
* Add multiple join clauses required for this entity {@see add_join}
*
* @param string[] $joins
* @return self
*/
final public function add_joins(array $joins): self {
foreach ($joins as $join) {
$this->add_join($join);
}
return $this;
}
/**
* Return entity joins
*
* @return string[]
*/
final public function get_joins(): array {
return array_values($this->joins);
}
/**
* Helper method for returning joins necessary for retrieving tags related to the current entity
*
* Both 'tag' and 'tag_instance' aliases must be returned by the entity {@see get_default_tables} method
*
* @param string $component
* @param string $itemtype
* @param string $itemidfield
* @return string[]
*/
final protected function get_tag_joins_for_entity(string $component, string $itemtype, string $itemidfield): array {
$taginstancealias = $this->get_table_alias('tag_instance');
$tagalias = $this->get_table_alias('tag');
return [
"LEFT JOIN {tag_instance} {$taginstancealias}
ON {$taginstancealias}.component = '{$component}'
AND {$taginstancealias}.itemtype = '{$itemtype}'
AND {$taginstancealias}.itemid = {$itemidfield}",
"LEFT JOIN {tag} {$tagalias}
ON {$tagalias}.id = {$taginstancealias}.tagid",
];
}
/**
* Add a column to the entity
*
* @param column $column
* @return self
*/
final protected function add_column(column $column): self {
$this->columns[$column->get_name()] = $column;
return $this;
}
/**
* Returns entity columns
*
* @return column[]
*/
final public function get_columns(): array {
return $this->columns;
}
/**
* Returns an entity column
*
* @param string $name
* @return column
* @throws coding_exception For invalid column name
*/
final public function get_column(string $name): column {
if (!array_key_exists($name, $this->columns)) {
throw new coding_exception('Invalid column name', $name);
}
return $this->columns[$name];
}
/**
* Add a filter to the entity
*
* @param filter $filter
* @return self
*/
final protected function add_filter(filter $filter): self {
$this->filters[$filter->get_name()] = $filter;
return $this;
}
/**
* Returns entity filters
*
* @return filter[]
*/
final public function get_filters(): array {
return $this->filters;
}
/**
* Returns an entity filter
*
* @param string $name
* @return filter
* @throws coding_exception For invalid filter name
*/
final public function get_filter(string $name): filter {
if (!array_key_exists($name, $this->filters)) {
throw new coding_exception('Invalid filter name', $name);
}
return $this->filters[$name];
}
/**
* Add a condition to the entity
*
* @param filter $condition
* @return $this
*/
final protected function add_condition(filter $condition): self {
$this->conditions[$condition->get_name()] = $condition;
return $this;
}
/**
* Returns entity conditions
*
* @return filter[]
*/
final public function get_conditions(): array {
return $this->conditions;
}
/**
* Returns an entity condition
*
* @param string $name
* @return filter
* @throws coding_exception For invalid condition name
*/
final public function get_condition(string $name): filter {
if (!array_key_exists($name, $this->conditions)) {
throw new coding_exception('Invalid condition name', $name);
}
return $this->conditions[$name];
}
}
@@ -0,0 +1,483 @@
<?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;
use theme_config;
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
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'course',
'context',
'tag_instance',
'tag',
];
}
/**
* 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
*
* @return string
*/
public function get_context_join(): string {
$coursealias = $this->get_table_alias('course');
$contextalias = $this->get_table_alias('context');
return "LEFT JOIN {context} {$contextalias}
ON {$contextalias}.contextlevel = " . CONTEXT_COURSE . "
AND {$contextalias}.instanceid = {$coursealias}.id";
}
/**
* Course fields.
*
* @return array
*/
protected function get_course_fields(): array {
return [
'fullname' => new lang_string('fullnamecourse'),
'shortname' => new lang_string('shortnamecourse'),
'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('theme'),
'enablecompletion' => new lang_string('enablecompletion', 'completion'),
'downloadcontent' => new lang_string('downloadcoursecontent', 'course'),
'timecreated' => new lang_string('timecreated', 'core_reportbuilder'),
'timemodified' => new lang_string('timemodified', 'core_reportbuilder'),
];
}
/**
* 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':
case 'timecreated':
case 'timemodified':
$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
*
* @return string[]
*/
public function get_tag_joins(): array {
return $this->get_tag_joins_for_entity('core', 'course', $this->get_table_alias('course') . '.id');
}
/**
* 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 {
global $DB;
$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) {
$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') {
$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') {
$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) {
$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(),
$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 {
return array_map(
fn(theme_config $theme) => $theme->get_theme_name(),
get_list_of_themes(),
);
}
/**
* 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);
}
if ($this->get_course_field_type($fieldname) === column::TYPE_BOOLEAN) {
return format::boolean_as_text($value);
}
// If the column has corresponding filter, determine the value from its options.
$options = $this->get_options_for($fieldname);
if ($options !== null && array_key_exists($value, $options)) {
return $options[$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);
}
}
@@ -0,0 +1,610 @@
<?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_helper;
use context_system;
use context_user;
use core\context;
use core_component;
use html_writer;
use lang_string;
use moodle_url;
use stdClass;
use theme_config;
use core_user\fields;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\filters\user as user_filter;
use core_reportbuilder\local\helpers\user_profile_fields;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* User entity class implementation.
*
* This entity defines all the user columns and filters to be used in any report.
*
* @package core_reportbuilder
* @copyright 2020 Sara Arjona <sara@moodle.com> based on Marina Glancy code.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'user',
'context',
'tag_instance',
'tag',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('entityuser', 'core_reportbuilder');
}
/**
* Initialise the entity, add all user fields and all 'visible' user profile fields
*
* @return base
*/
public function initialise(): base {
$userprofilefields = $this->get_user_profile_fields();
$columns = array_merge($this->get_all_columns(), $userprofilefields->get_columns());
foreach ($columns as $column) {
$this->add_column($column);
}
$filters = array_merge($this->get_all_filters(), $userprofilefields->get_filters());
foreach ($filters as $filter) {
$this->add_filter($filter);
}
$conditions = array_merge($this->get_all_filters(), $userprofilefields->get_filters());
foreach ($conditions as $condition) {
$this->add_condition($condition);
}
return $this;
}
/**
* Get user profile fields helper instance
*
* @return user_profile_fields
*/
protected function get_user_profile_fields(): user_profile_fields {
$userprofilefields = new user_profile_fields($this->get_table_alias('user') . '.id', $this->get_entity_name());
$userprofilefields->add_joins($this->get_joins());
return $userprofilefields;
}
/**
* Returns column that corresponds to the given identity field, profile field identifiers will be converted to those
* used by the {@see user_profile_fields} helper
*
* @param string $identityfield Field from the user table, or a custom profile field
* @return column
*/
public function get_identity_column(string $identityfield): column {
if (preg_match(fields::PROFILE_FIELD_REGEX, $identityfield, $matches)) {
$identityfield = 'profilefield_' . $matches[1];
}
return $this->get_column($identityfield);
}
/**
* Returns columns that correspond to the site configured identity fields
*
* @param context $context
* @param string[] $excluding
* @return column[]
*/
public function get_identity_columns(context $context, array $excluding = []): array {
$identityfields = fields::for_identity($context)->excluding(...$excluding)->get_required_fields();
return array_map([$this, 'get_identity_column'], $identityfields);
}
/**
* Returns filter that corresponds to the given identity field, profile field identifiers will be converted to those
* used by the {@see user_profile_fields} helper
*
* @param string $identityfield Field from the user table, or a custom profile field
* @return filter
*/
public function get_identity_filter(string $identityfield): filter {
if (preg_match(fields::PROFILE_FIELD_REGEX, $identityfield, $matches)) {
$identityfield = 'profilefield_' . $matches[1];
}
return $this->get_filter($identityfield);
}
/**
* Returns filters that correspond to the site configured identity fields
*
* @param context $context
* @param string[] $excluding
* @return filter[]
*/
public function get_identity_filters(context $context, array $excluding = []): array {
$identityfields = fields::for_identity($context)->excluding(...$excluding)->get_required_fields();
return array_map([$this, 'get_identity_filter'], $identityfields);
}
/**
* Return joins necessary for retrieving tags
*
* @return string[]
*/
public function get_tag_joins(): array {
return $this->get_tag_joins_for_entity('core', 'user', $this->get_table_alias('user') . '.id');
}
/**
* 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 {
global $DB;
$usertablealias = $this->get_table_alias('user');
$contexttablealias = $this->get_table_alias('context');
$fullnameselect = self::get_name_fields_select($usertablealias);
$fullnamesort = explode(', ', $fullnameselect);
$userpictureselect = fields::for_userpic()->get_sql($usertablealias, false, '', '', false)->selects;
$viewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance());
// Fullname column.
$columns[] = (new column(
'fullname',
new lang_string('fullname'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_fields($fullnameselect)
->set_type(column::TYPE_TEXT)
->set_is_sortable($this->is_sortable('fullname'), $fullnamesort)
->add_callback(static function(?string $value, stdClass $row) use ($viewfullnames): string {
if ($value === null) {
return '';
}
// Ensure we populate all required name properties.
$namefields = fields::get_name_fields();
foreach ($namefields as $namefield) {
$row->{$namefield} = $row->{$namefield} ?? '';
}
return fullname($row, $viewfullnames);
});
// Formatted fullname columns (with link, picture or both).
$fullnamefields = [
'fullnamewithlink' => new lang_string('userfullnamewithlink', 'core_reportbuilder'),
'fullnamewithpicture' => new lang_string('userfullnamewithpicture', 'core_reportbuilder'),
'fullnamewithpicturelink' => new lang_string('userfullnamewithpicturelink', 'core_reportbuilder'),
];
foreach ($fullnamefields as $fullnamefield => $fullnamelang) {
$column = (new column(
$fullnamefield,
$fullnamelang,
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_fields($fullnameselect)
->add_field("{$usertablealias}.id")
->set_type(column::TYPE_TEXT)
->set_is_sortable($this->is_sortable($fullnamefield), $fullnamesort)
->add_callback(static function(?string $value, stdClass $row) use ($fullnamefield, $viewfullnames): string {
global $OUTPUT;
if ($value === null) {
return '';
}
// Ensure we populate all required name properties.
$namefields = fields::get_name_fields();
foreach ($namefields as $namefield) {
$row->{$namefield} = $row->{$namefield} ?? '';
}
if ($fullnamefield === 'fullnamewithlink') {
return html_writer::link(new moodle_url('/user/profile.php', ['id' => $row->id]),
fullname($row, $viewfullnames));
}
if ($fullnamefield === 'fullnamewithpicture') {
return $OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) .
fullname($row, $viewfullnames);
}
if ($fullnamefield === 'fullnamewithpicturelink') {
return html_writer::link(new moodle_url('/user/profile.php', ['id' => $row->id]),
$OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) .
fullname($row, $viewfullnames));
}
return $value;
});
// Picture fields need some more data.
if (strpos($fullnamefield, 'picture') !== false) {
$column->add_fields($userpictureselect);
}
$columns[] = $column;
}
// Picture column.
$columns[] = (new column(
'picture',
new lang_string('userpicture', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_fields($userpictureselect)
->set_type(column::TYPE_INTEGER)
->set_is_sortable($this->is_sortable('picture'))
// It doesn't make sense to offer integer aggregation methods for this column.
->set_disabled_aggregation(['avg', 'max', 'min', 'sum'])
->add_callback(static function ($value, stdClass $row): string {
global $OUTPUT;
return !empty($row->id) ? $OUTPUT->user_picture($row, ['link' => false, 'alttext' => false]) : '';
});
// Add all other user fields.
$userfields = $this->get_user_fields();
foreach ($userfields as $userfield => $userfieldlang) {
$columntype = $this->get_user_field_type($userfield);
$columnfieldsql = "{$usertablealias}.{$userfield}";
if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') {
$columnfieldsql = $DB->sql_order_by_text($columnfieldsql, 1024);
}
$column = (new column(
$userfield,
$userfieldlang,
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type($columntype)
->add_field($columnfieldsql, $userfield)
->set_is_sortable($this->is_sortable($userfield))
->add_callback([$this, 'format'], $userfield);
// Join on the context table so that we can use it for formatting these columns later.
if ($userfield === 'description') {
$column
->add_join("LEFT JOIN {context} {$contexttablealias}
ON {$contexttablealias}.contextlevel = " . CONTEXT_USER . "
AND {$contexttablealias}.instanceid = {$usertablealias}.id")
->add_fields("{$usertablealias}.descriptionformat, {$usertablealias}.id")
->add_fields(context_helper::get_preload_record_columns_sql($contexttablealias));
}
$columns[] = $column;
}
return $columns;
}
/**
* 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 = [
'description',
'picture',
];
return !in_array($fieldname, $nonsortable);
}
/**
* Formats the user 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 {
global $CFG;
if ($this->get_user_field_type($fieldname) === column::TYPE_BOOLEAN) {
return format::boolean_as_text($value);
}
if ($this->get_user_field_type($fieldname) === column::TYPE_TIMESTAMP) {
return format::userdate($value, $row);
}
// If the column has corresponding filter, determine the value from its options.
$options = $this->get_options_for($fieldname);
if ($options !== null && array_key_exists($value, $options)) {
return $options[$value];
}
if ($fieldname === 'description') {
if (empty($row->id)) {
return '';
}
require_once("{$CFG->libdir}/filelib.php");
context_helper::preload_from_record($row);
$context = context_user::instance($row->id);
$description = file_rewrite_pluginfile_urls($value, 'pluginfile.php', $context->id, 'user', 'profile', null);
return format_text($description, $row->descriptionformat, ['context' => $context->id]);
}
return s($value);
}
/**
* Returns a SQL statement to select all user fields necessary for fullname() function
*
* Note the implementation here is similar to {@see fields::get_sql_fullname} but without concatenation
*
* @param string $usertablealias
* @return string
*/
public static function get_name_fields_select(string $usertablealias = 'u'): string {
$namefields = fields::get_name_fields(true);
// Create a dummy user object containing all name fields.
$dummyuser = (object) array_combine($namefields, $namefields);
$viewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance());
$dummyfullname = fullname($dummyuser, $viewfullnames);
// Extract any name fields from the fullname format in the order that they appear.
$matchednames = array_values(order_in_string($namefields, $dummyfullname));
$userfields = array_map(static function(string $userfield) use ($usertablealias): string {
if (!empty($usertablealias)) {
$userfield = "{$usertablealias}.{$userfield}";
}
return $userfield;
}, $matchednames);
return implode(', ', $userfields);
}
/**
* User fields
*
* @return lang_string[]
*/
protected function get_user_fields(): array {
return [
'firstname' => new lang_string('firstname'),
'lastname' => new lang_string('lastname'),
'email' => new lang_string('email'),
'city' => new lang_string('city'),
'country' => new lang_string('country'),
'theme' => new lang_string('theme'),
'description' => new lang_string('description'),
'firstnamephonetic' => new lang_string('firstnamephonetic'),
'lastnamephonetic' => new lang_string('lastnamephonetic'),
'middlename' => new lang_string('middlename'),
'alternatename' => new lang_string('alternatename'),
'idnumber' => new lang_string('idnumber'),
'institution' => new lang_string('institution'),
'department' => new lang_string('department'),
'phone1' => new lang_string('phone1'),
'phone2' => new lang_string('phone2'),
'address' => new lang_string('address'),
'lastaccess' => new lang_string('lastaccess'),
'suspended' => new lang_string('suspended'),
'confirmed' => new lang_string('confirmed', 'admin'),
'username' => new lang_string('username'),
'auth' => new lang_string('authentication', 'moodle'),
'moodlenetprofile' => new lang_string('moodlenetprofile', 'user'),
'timecreated' => new lang_string('timecreated', 'core_reportbuilder'),
'timemodified' => new lang_string('timemodified', 'core_reportbuilder'),
'lastip' => new lang_string('lastip'),
];
}
/**
* Return appropriate column type for given user field
*
* @param string $userfield
* @return int
*/
protected function get_user_field_type(string $userfield): int {
switch ($userfield) {
case 'description':
$fieldtype = column::TYPE_LONGTEXT;
break;
case 'confirmed':
case 'suspended':
$fieldtype = column::TYPE_BOOLEAN;
break;
case 'lastaccess':
case 'timecreated':
case 'timemodified':
$fieldtype = column::TYPE_TIMESTAMP;
break;
default:
$fieldtype = column::TYPE_TEXT;
break;
}
return $fieldtype;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
global $DB;
$filters = [];
$tablealias = $this->get_table_alias('user');
// Fullname filter.
$canviewfullnames = has_capability('moodle/site:viewfullnames', context_system::instance());
[$fullnamesql, $fullnameparams] = fields::get_sql_fullname($tablealias, $canviewfullnames);
$filters[] = (new filter(
text::class,
'fullname',
new lang_string('fullname'),
$this->get_entity_name(),
$fullnamesql,
$fullnameparams
))
->add_joins($this->get_joins());
// User fields filters.
$fields = $this->get_user_fields();
foreach ($fields as $field => $name) {
$filterfieldsql = "{$tablealias}.{$field}";
if ($this->get_user_field_type($field) === column::TYPE_LONGTEXT) {
$filterfieldsql = $DB->sql_cast_to_char($filterfieldsql);
}
$optionscallback = [static::class, 'get_options_for_' . $field];
if (is_callable($optionscallback)) {
$classname = select::class;
} else if ($this->get_user_field_type($field) === column::TYPE_BOOLEAN) {
$classname = boolean_select::class;
} else if ($this->get_user_field_type($field) === column::TYPE_TIMESTAMP) {
$classname = date::class;
} else {
$classname = text::class;
}
$filter = (new filter(
$classname,
$field,
$name,
$this->get_entity_name(),
$filterfieldsql
))
->add_joins($this->get_joins());
// Populate filter options by callback, if available.
if (is_callable($optionscallback)) {
$filter->set_options_callback($optionscallback);
}
$filters[] = $filter;
}
// User select filter.
$filters[] = (new filter(
user_filter::class,
'userselect',
new lang_string('userselect', '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 auth
*
* @return string[]
*/
public static function get_options_for_auth(): array {
$authlist = array_keys(core_component::get_plugin_list('auth'));
return array_map(
fn(string $auth) => get_auth_plugin($auth)->get_title(),
array_combine($authlist, $authlist),
);
}
/**
* List of options for the field country.
*
* @return string[]
*/
public static function get_options_for_country(): array {
return get_string_manager()->get_list_of_countries();
}
/**
* List of options for the field theme.
*
* @return string[]
*/
public static function get_options_for_theme(): array {
return array_map(
fn(theme_config $theme) => $theme->get_theme_name(),
get_list_of_themes(),
);
}
}
@@ -0,0 +1,88 @@
<?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\filters;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
/**
* Autocomplete report filter
*
* @package core_reportbuilder
* @copyright 2022 Nathan Nguyen <nathannguyen@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class autocomplete extends base {
/**
* Return the options for the filter as an array, to be used to populate the select input field
*
* @return array
*/
protected function get_select_options(): array {
return (array) $this->filter->get_options();
}
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$operatorlabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());
$values = [0 => ''] + $this->get_select_options();
$options = ['multiple' => true];
$mform->addElement('autocomplete', $this->name . '_values', $operatorlabel, $values, $options)
->setHiddenLabel(true);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
global $DB;
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
$invalues = $values["{$this->name}_values"] ?? [];
if (empty($invalues)) {
return ['', []];
}
[$insql, $inparams] = $DB->get_in_or_equal($invalues, SQL_PARAMS_NAMED, database::generate_param_name('_'));
return ["{$fieldsql} $insql", array_merge($params, $inparams)];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_values" => [1],
];
}
}
@@ -0,0 +1,136 @@
<?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\filters;
use MoodleQuickForm;
use core_reportbuilder\local\report\filter;
use core_reportbuilder\local\models\filter as filter_model;
/**
* Base class for all report filters
*
* Filters provide a form for collecting user input, and then return appropriate SQL fragments based on these values
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base {
/** @var filter $filter */
protected $filter;
/** @var string $name */
protected $name;
/**
* Do not allow the constructor to be called directly or overridden
*
* @param filter $filter
*/
private function __construct(filter $filter) {
$this->filter = $filter;
$this->name = $filter->get_unique_identifier();
}
/**
* Creates an instance of a filter type, based on supplied report filter instance
*
* The report filter instance is used by reports/entities to define what should be filtered against, e.g. a SQL fragment
*
* @param filter $filter The report filter instance
* @return static
*/
final public static function create(filter $filter): self {
$filterclass = $filter->get_filter_class();
return new $filterclass($filter);
}
/**
* Returns the filter header
*
* @return string
*/
final public function get_header(): string {
return $this->filter->get_header();
}
/**
* Returns the filter's entity name
*
* @return string
*/
final public function get_entity_name(): string {
return $this->filter->get_entity_name();
}
/**
* Returns the filter persistent
*
* Note that filters for system reports don't store a persistent and will return null.
*
* @return filter_model|null
*/
final public function get_filter_persistent(): ?filter_model {
return $this->filter->get_persistent();
}
/**
* Adds filter-specific form elements
*
* @param MoodleQuickForm $mform
*/
abstract public function setup_form(MoodleQuickForm $mform): void;
/**
* Returns the filter clauses to be used with SQL where
*
* Ideally the field SQL should be included only once in the returned expression, however if that is unavoidable then
* use the {@see filter::get_field_sql_and_params} helper to ensure uniqueness of any parameters included within
*
* @param array $values
* @return array [$sql, [...$params]]
*/
abstract public function get_sql_filter(array $values): array;
/**
* Given an array of current filter values for the report, determine whether the filter would apply to the report (i.e. user
* has configured it from it's initial "Any value" state). A filter would typically be considered applied if it returns SQL
* filter clauses, but child classes may override this method if they use different logic
*
* @param array $values
* @return bool
*/
public function applies_to_values(array $values): bool {
[$filtersql] = $this->get_sql_filter($values);
return $filtersql !== '';
}
/**
* Return sample filter values, that when applied to a report would activate the filter - that is, cause the filter to return
* SQL snippet. Should be overridden in child classes, to ensure compatibility with stress tests of reports
*
* @return array
*/
public function get_sample_values(): array {
return [];
}
}
@@ -0,0 +1,115 @@
<?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\filters;
use lang_string;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
/**
* Boolean report filter
*
* This filter accepts an expression that evaluates to 1 or 0, either a simple field such as "u.suspended", or a more complex
* expression such as "CASE WHEN <EXPRESSION> THEN 1 ELSE 0 END"
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class boolean_select extends base {
/** @var int Any value */
public const ANY_VALUE = 0;
/** @var int Checked */
public const CHECKED = 1;
/** @var int Not checked */
public const NOT_CHECKED = 2;
/**
* Return an array of operators available for this filter
*
* @return lang_string[]
*/
private function get_operators(): array {
$operators = [
self::ANY_VALUE => new lang_string('filterisanyvalue', 'core_reportbuilder'),
self::CHECKED => new lang_string('yes'),
self::NOT_CHECKED => new lang_string('no'),
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
$mform->addElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators())
->setHiddenLabel(true);
$mform->setType("{$this->name}_operator", PARAM_INT);
$mform->setDefault("{$this->name}_operator", self::ANY_VALUE);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
$paramname = database::generate_param_name();
$operator = $values["{$this->name}_operator"] ?? self::ANY_VALUE;
switch ($operator) {
case self::CHECKED:
$fieldsql .= " = :{$paramname}";
$params[$paramname] = 1;
break;
case self::NOT_CHECKED:
$fieldsql .= " = :{$paramname}";
$params[$paramname] = 0;
break;
default:
// Invalid or inactive filter.
return ['', []];
}
return [$fieldsql, $params];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_operator" => self::CHECKED,
];
}
}
@@ -0,0 +1,138 @@
<?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\filters;
use core_course_category;
use lang_string;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
/**
* Course category report filter
*
* The following optional array property can be passed to the {@see \core_reportbuilder\local\report\filter::set_options} method
* when defining this filter, to define the capabilities passed to {@see \core_course_category::make_categories_list}
*
* ['requiredcapabilities' => '...']
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class category extends base {
/** @var int Category is equal to */
public const EQUAL_TO = 0;
/** @var int Category is not equal to */
public const NOT_EQUAL_TO = 1;
/**
* Returns an array of comparison operators
*
* @return array
*/
private function get_operators(): array {
$operators = [
self::EQUAL_TO => new lang_string('filterisequalto', 'core_reportbuilder'),
self::NOT_EQUAL_TO => new lang_string('filterisnotequalto', 'core_reportbuilder'),
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
$mform->addElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators())
->setHiddenLabel(true);
// See MDL-74627: in order to set the default value to "No selection" we need to prepend an empty value.
$requiredcapabilities = $this->filter->get_options()['requiredcapabilities'] ?? '';
$categories = [0 => ''] + core_course_category::make_categories_list($requiredcapabilities);
$valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());
$mform->addElement('autocomplete', "{$this->name}_value", $valuelabel, $categories)->setHiddenLabel(true);
$mform->addElement('advcheckbox', "{$this->name}_subcategories", get_string('includesubcategories'));
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
global $DB;
[$fieldsql, $params] = $this->filter->get_field_sql_and_params();
$operator = (int) ($values["{$this->name}_operator"] ?? self::EQUAL_TO);
$category = (int) ($values["{$this->name}_value"] ?? 0);
$subcategories = !empty($values["{$this->name}_subcategories"]);
// Invalid or inactive filter.
if (empty($category)) {
return ['', []];
}
// Initial matching on selected category.
$paramcategory = database::generate_param_name();
$params[$paramcategory] = $category;
$sql = "{$fieldsql} = :{$paramcategory}";
// Sub-category matching on path of selected category.
if ($subcategories) {
// We need to re-use the original filter SQL here, while ensuring parameter uniqueness is preserved.
[$fieldsql, $params1] = $this->filter->get_field_sql_and_params(1);
$params = array_merge($params, $params1);
$paramcategorypath = database::generate_param_name();
$params[$paramcategorypath] = "%/{$category}/%";
$sql .= " OR {$fieldsql} IN (
SELECT id
FROM {course_categories}
WHERE " . $DB->sql_like('path', ":{$paramcategorypath}") . "
)";
}
// If specified "Not equal to", then negate the entire clause.
if ($operator === self::NOT_EQUAL_TO) {
$sql = "NOT ({$sql})";
}
return [$sql, $params];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_value" => 1,
];
}
}
@@ -0,0 +1,85 @@
<?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\filters;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
/**
* Cohort selector filter class implementation
*
* @package core_reportbuilder
* @copyright 2024 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort extends base {
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$mform->addElement(
'cohort',
"{$this->name}_values",
get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()),
[
'multiple' => true,
],
)->setHiddenLabel(true);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
global $DB;
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
$cohortids = $values["{$this->name}_values"] ?? [];
if (empty($cohortids)) {
return ['', []];
}
[$cohortselect, $cohortparams] = $DB->get_in_or_equal(
$cohortids,
SQL_PARAMS_NAMED,
database::generate_param_name('_'),
);
return ["{$fieldsql} $cohortselect", array_merge($params, $cohortparams)];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_values" => [1],
];
}
}
@@ -0,0 +1,80 @@
<?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\filters;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
/**
* Course selector filter class implementation
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_selector extends base {
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$operatorlabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());
$options = [
'multiple' => true,
];
$mform->addElement('course', $this->name . '_values', $operatorlabel, $options)
->setHiddenLabel(true);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
global $DB;
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
$courseids = $values["{$this->name}_values"] ?? [];
if (empty($courseids)) {
return ['', []];
}
[$courseselect, $courseparams] = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, database::generate_param_name('_'));
return ["{$fieldsql} $courseselect", array_merge($params, $courseparams)];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_values" => [1],
];
}
}
@@ -0,0 +1,358 @@
<?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\filters;
use DateTimeImmutable;
use lang_string;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
/**
* Date report filter
*
* This filter accepts a unix timestamp to perform date filtering on
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class date extends base {
/** @var int Any value */
public const DATE_ANY = 0;
/** @var int Non-empty (positive) value */
public const DATE_NOT_EMPTY = 1;
/** @var int Empty (zero) value */
public const DATE_EMPTY = 2;
/** @var int Date within defined range */
public const DATE_RANGE = 3;
/** @var int Date in the last [X relative date unit(s)] */
public const DATE_LAST = 4;
/** @var int Date in the previous [X relative date unit(s)] Kept for backwards compatibility */
public const DATE_PREVIOUS = self::DATE_LAST;
/** @var int Date in current [relative date unit] */
public const DATE_CURRENT = 5;
/** @var int Date in the next [X relative date unit(s)] */
public const DATE_NEXT = 6;
/** @var int Date in the past */
public const DATE_PAST = 7;
/** @var int Date in the future */
public const DATE_FUTURE = 8;
/** @var int Date before [X relative date unit(s)] */
public const DATE_BEFORE = 9;
/** @var int Date after [X relative date unit(s)] */
public const DATE_AFTER = 10;
/** @var int Relative date unit for an hour */
public const DATE_UNIT_HOUR = 0;
/** @var int Relative date unit for a day */
public const DATE_UNIT_DAY = 1;
/** @var int Relative date unit for a week */
public const DATE_UNIT_WEEK = 2;
/** @var int Relative date unit for a month */
public const DATE_UNIT_MONTH = 3;
/** @var int Relative date unit for a month */
public const DATE_UNIT_YEAR = 4;
/**
* Return an array of operators available for this filter
*
* @return lang_string[]
*/
private function get_operators(): array {
$operators = [
self::DATE_ANY => new lang_string('filterisanyvalue', 'core_reportbuilder'),
self::DATE_NOT_EMPTY => new lang_string('filterisnotempty', 'core_reportbuilder'),
self::DATE_EMPTY => new lang_string('filterisempty', 'core_reportbuilder'),
self::DATE_RANGE => new lang_string('filterrange', 'core_reportbuilder'),
self::DATE_BEFORE => new lang_string('filterdatebefore', 'core_reportbuilder'),
self::DATE_AFTER => new lang_string('filterdateafter', 'core_reportbuilder'),
self::DATE_LAST => new lang_string('filterdatelast', 'core_reportbuilder'),
self::DATE_CURRENT => new lang_string('filterdatecurrent', 'core_reportbuilder'),
self::DATE_NEXT => new lang_string('filterdatenext', 'core_reportbuilder'),
self::DATE_PAST => new lang_string('filterdatepast', 'core_reportbuilder'),
self::DATE_FUTURE => new lang_string('filterdatefuture', 'core_reportbuilder'),
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
// Operator selector.
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
$typesnounit = [self::DATE_ANY, self::DATE_NOT_EMPTY, self::DATE_EMPTY, self::DATE_RANGE,
self::DATE_PAST, self::DATE_FUTURE];
$elements[] = $mform->createElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators());
$mform->setType("{$this->name}_operator", PARAM_INT);
$mform->setDefault("{$this->name}_operator", self::DATE_ANY);
// Value selector for last and next operators.
$valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());
$elements[] = $mform->createElement('text', "{$this->name}_value", $valuelabel, ['size' => 3]);
$mform->setType("{$this->name}_value", PARAM_INT);
$mform->setDefault("{$this->name}_value", 1);
$mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'in', array_merge($typesnounit, [self::DATE_CURRENT]));
// Unit selector for last and next operators.
$unitlabel = get_string('filterfieldunit', 'core_reportbuilder', $this->get_header());
$units = [
self::DATE_UNIT_HOUR => get_string('filterdatehours', 'core_reportbuilder'),
self::DATE_UNIT_DAY => get_string('filterdatedays', 'core_reportbuilder'),
self::DATE_UNIT_WEEK => get_string('filterdateweeks', 'core_reportbuilder'),
self::DATE_UNIT_MONTH => get_string('filterdatemonths', 'core_reportbuilder'),
self::DATE_UNIT_YEAR => get_string('filterdateyears', 'core_reportbuilder'),
];
$elements[] = $mform->createElement('select', "{$this->name}_unit", $unitlabel, $units);
$mform->setType("{$this->name}_unit", PARAM_INT);
$mform->setDefault("{$this->name}_unit", self::DATE_UNIT_DAY);
$mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'in', $typesnounit);
// Add operator/value/unit group.
$mform->addGroup($elements, "{$this->name}_group", $this->get_header(), '', false)
->setHiddenLabel(true);
// Date selectors for range operator.
$mform->addElement('date_selector', "{$this->name}_from", get_string('filterdatefrom', 'core_reportbuilder'),
['optional' => true]);
$mform->setType("{$this->name}_from", PARAM_INT);
$mform->setDefault("{$this->name}_from", 0);
$mform->hideIf("{$this->name}_from", "{$this->name}_operator", 'neq', self::DATE_RANGE);
$mform->addElement('date_selector', "{$this->name}_to", get_string('filterdateto', 'core_reportbuilder'),
['optional' => true]);
$mform->setType("{$this->name}_to", PARAM_INT);
$mform->setDefault("{$this->name}_to", 0);
$mform->hideIf("{$this->name}_to", "{$this->name}_operator", 'neq', self::DATE_RANGE);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
$operator = (int) ($values["{$this->name}_operator"] ?? self::DATE_ANY);
$dateunitvalue = (int) ($values["{$this->name}_value"] ?? 1);
$dateunit = (int) ($values["{$this->name}_unit"] ?? self::DATE_UNIT_DAY);
switch ($operator) {
case self::DATE_NOT_EMPTY:
$sql = "COALESCE({$fieldsql}, 0) <> 0";
break;
case self::DATE_EMPTY:
$sql = "COALESCE({$fieldsql}, 0) = 0";
break;
case self::DATE_RANGE:
$sql = '';
$datefrom = (int)($values["{$this->name}_from"] ?? 0);
$dateto = (int)($values["{$this->name}_to"] ?? 0);
[$paramdatefrom, $paramdateto] = database::generate_param_names(2);
if ($datefrom > 0 && $dateto > 0) {
$sql = "{$fieldsql} BETWEEN :{$paramdatefrom} AND :{$paramdateto}";
$params[$paramdatefrom] = $datefrom;
$params[$paramdateto] = $dateto;
} else if ($datefrom > 0) {
$sql = "{$fieldsql} >= :{$paramdatefrom}";
$params[$paramdatefrom] = $datefrom;
} else if ($dateto > 0) {
$sql = "{$fieldsql} < :{$paramdateto}";
$params[$paramdateto] = $dateto;
}
break;
case self::DATE_BEFORE:
$param = database::generate_param_name();
// We can use the start date of the "Last" operator as the end date here.
$sql = "{$fieldsql} < :{$param}";
$params[$param] = self::get_relative_timeframe(self::DATE_LAST, $dateunitvalue, $dateunit)[0];
break;
case self::DATE_AFTER:
$param = database::generate_param_name();
// We can use the end date of the "Next" operator as the start date here.
$sql = "{$fieldsql} > :{$param}";
$params[$param] = self::get_relative_timeframe(self::DATE_NEXT, $dateunitvalue, $dateunit)[1];
break;
// Relative helper method can handle these three cases.
case self::DATE_LAST:
case self::DATE_CURRENT:
case self::DATE_NEXT:
// Last and next operators require a unit value greater than zero.
if ($operator !== self::DATE_CURRENT && $dateunitvalue === 0) {
return ['', []];
}
// Generate parameters and SQL clause for the relative date comparison.
[$paramdatefrom, $paramdateto] = database::generate_param_names(2);
$sql = "{$fieldsql} BETWEEN :{$paramdatefrom} AND :{$paramdateto}";
[
$params[$paramdatefrom],
$params[$paramdateto],
] = self::get_relative_timeframe($operator, $dateunitvalue, $dateunit);
break;
case self::DATE_PAST:
$param = database::generate_param_name();
$sql = "{$fieldsql} < :{$param}";
$params[$param] = time();
break;
case self::DATE_FUTURE:
$param = database::generate_param_name();
$sql = "{$fieldsql} > :{$param}";
$params[$param] = time();
break;
default:
// Invalid or inactive filter.
return ['', []];
}
return [$sql, $params];
}
/**
* Return start and end time of given relative date period
*
* @param int $operator One of the ::DATE_LAST/CURRENT/NEXT constants
* @param int $dateunitvalue Unit multiplier of the date unit
* @param int $dateunit One of the ::DATE_UNIT_* constants
* @return int[] Timestamps representing the start/end of timeframe
*/
private static function get_relative_timeframe(int $operator, int $dateunitvalue, int $dateunit): array {
// Initialise start/end time to now.
$datestart = $dateend = new DateTimeImmutable();
switch ($dateunit) {
case self::DATE_UNIT_HOUR:
if ($operator === self::DATE_CURRENT) {
$hour = (int) $datestart->format('G');
$datestart = $datestart->setTime($hour, 0);
$dateend = $dateend->setTime($hour, 59, 59);
} else if ($operator === self::DATE_LAST) {
$datestart = $datestart->modify("-{$dateunitvalue} hour");
} else if ($operator === self::DATE_NEXT) {
$dateend = $dateend->modify("+{$dateunitvalue} hour");
}
break;
case self::DATE_UNIT_DAY:
if ($operator === self::DATE_CURRENT) {
$datestart = $datestart->setTime(0, 0);
$dateend = $dateend->setTime(23, 59, 59);
} else if ($operator === self::DATE_LAST) {
$datestart = $datestart->modify("-{$dateunitvalue} day");
} else if ($operator === self::DATE_NEXT) {
$dateend = $dateend->modify("+{$dateunitvalue} day");
}
break;
case self::DATE_UNIT_WEEK:
if ($operator === self::DATE_CURRENT) {
// The first day of the week is determined by site calendar configuration/preferences.
$startweekday = \core_calendar\type_factory::get_calendar_instance()->get_starting_weekday();
$weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
// If calculated start of week is after today (today is Tues/start of week is Weds), move back a week.
$datestartnow = $datestart->getTimestamp();
$datestart = $datestart->modify($weekdays[$startweekday] . ' this week')->setTime(0, 0);
if ($datestart->getTimestamp() > $datestartnow) {
$datestart = $datestart->modify('-1 week');
}
$dateend = $datestart->modify('+6 day')->setTime(23, 59, 59);
} else if ($operator === self::DATE_LAST) {
$datestart = $datestart->modify("-{$dateunitvalue} week");
} else if ($operator === self::DATE_NEXT) {
$dateend = $dateend->modify("+{$dateunitvalue} week");
}
break;
case self::DATE_UNIT_MONTH:
if ($operator === self::DATE_CURRENT) {
$datestart = $datestart->modify('first day of this month')->setTime(0, 0);
$dateend = $dateend->modify('last day of this month')->setTime(23, 59, 59);
} else if ($operator === self::DATE_LAST) {
$datestart = $datestart->modify("-{$dateunitvalue} month");
} else if ($operator === self::DATE_NEXT) {
$dateend = $dateend->modify("+{$dateunitvalue} month");
}
break;
case self::DATE_UNIT_YEAR:
if ($operator === self::DATE_CURRENT) {
$datestart = $datestart->modify('first day of january this year')->setTime(0, 0);
$dateend = $dateend->modify('last day of december this year')->setTime(23, 59, 59);
} else if ($operator === self::DATE_LAST) {
$datestart = $datestart->modify("-{$dateunitvalue} year");
} else if ($operator === self::DATE_NEXT) {
$dateend = $dateend->modify("+{$dateunitvalue} year");
}
break;
}
return [
$datestart->getTimestamp(),
$dateend->getTimestamp(),
];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_operator" => self::DATE_CURRENT,
"{$this->name}_unit" => self::DATE_UNIT_WEEK,
];
}
}
@@ -0,0 +1,152 @@
<?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\filters;
use lang_string;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
/**
* Duration report filter
*
* This filter accepts a number of seconds to perform filtering on
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class duration extends base {
/** @var int Any value */
public const DURATION_ANY = 0;
/** @var int Maximum duration */
public const DURATION_MAXIMUM = 1;
/** @var int Minimum duration */
public const DURATION_MINIMUM = 2;
/**
* Return an array of operators available for this filter
*
* @return lang_string[]
*/
private function get_operators(): array {
$operators = [
self::DURATION_ANY => new lang_string('filterisanyvalue', 'core_reportbuilder'),
self::DURATION_MAXIMUM => new lang_string('filterlessthan', 'core_reportbuilder'),
self::DURATION_MINIMUM => new lang_string('filtergreaterthan', 'core_reportbuilder'),
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$elements = [];
// Operator.
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
$elements[] = $mform->createElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators());
$mform->setType("{$this->name}_operator", PARAM_INT);
$mform->setDefault("{$this->name}_operator", self::DURATION_ANY);
// Value.
$valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());
$elements[] = $mform->createElement('text', "{$this->name}_value", $valuelabel, ['size' => 3]);
$mform->setType("{$this->name}_value", PARAM_FLOAT);
$mform->setDefault("{$this->name}_value", 0);
$mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DURATION_ANY);
// Unit.
$unitlabel = get_string('filterfieldunit', 'core_reportbuilder', $this->get_header());
$units = [
1 => get_string('filterdateseconds', 'core_reportbuilder'),
MINSECS => get_string('filterdateminutes', 'core_reportbuilder'),
HOURSECS => get_string('filterdatehours', 'core_reportbuilder'),
DAYSECS => get_string('filterdatedays', 'core_reportbuilder'),
WEEKSECS => get_string('filterdateweeks', 'core_reportbuilder'),
];
$elements[] = $mform->createElement('select', "{$this->name}_unit", $unitlabel, $units);
$mform->setType("{$this->name}_unit", PARAM_INT);
$mform->setDefault("{$this->name}_unit", 1);
$mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DURATION_ANY);
$mform->addGroup($elements, "{$this->name}_group", $this->get_header(), '', false)
->setHiddenLabel(true);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
$durationvalue = unformat_float($values["{$this->name}_value"] ?? 0);
$durationunit = (int) ($values["{$this->name}_unit"] ?? 0);
$operator = $values["{$this->name}_operator"] ?? self::DURATION_ANY;
switch ($operator) {
case self::DURATION_MAXIMUM:
$paramduration = database::generate_param_name();
$sql = "{$fieldsql} <= :{$paramduration}";
$params[$paramduration] = $durationvalue * $durationunit;
break;
case self::DURATION_MINIMUM:
$paramduration = database::generate_param_name();
$sql = "{$fieldsql} >= :{$paramduration}";
$params[$paramduration] = $durationvalue * $durationunit;
break;
default:
// Invalid or inactive filter.
return ['', []];
}
return [$sql, $params];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_operator" => self::DURATION_MAXIMUM,
"{$this->name}_value" => 2,
"{$this->name}_unit" => MINSECS,
];
}
}
@@ -0,0 +1,152 @@
<?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\filters;
use core_reportbuilder\local\helpers\database;
use lang_string;
use MoodleQuickForm;
/**
* Filesize report filter
*
* @package core_reportbuilder
* @copyright 2023 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filesize extends base {
/** @var int Any value */
public const ANY_VALUE = 0;
/** @var int Less than */
public const LESS_THAN = 3;
/** @var int Greater than */
public const GREATER_THAN = 4;
/** @var int Bytes */
public const SIZE_UNIT_BYTE = 1;
/** @var int Kilobytes */
public const SIZE_UNIT_KILOBYTE = 1024;
/** @var int Megabytes */
public const SIZE_UNIT_MEGABYTE = self::SIZE_UNIT_KILOBYTE * 1024;
/** @var int Gigabytes */
public const SIZE_UNIT_GIGABYTE = self::SIZE_UNIT_MEGABYTE * 1024;
/**
* Return an array of operators available for this filter
*
* @return lang_string[]
*/
private function get_operators(): array {
$operators = [
self::ANY_VALUE => new lang_string('filterisanyvalue', 'core_reportbuilder'),
self::LESS_THAN => new lang_string('filterlessthan', 'core_reportbuilder'),
self::GREATER_THAN => new lang_string('filtergreaterthan', 'core_reportbuilder'),
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
// Operator selector.
$elements[] = $mform->createElement('select', "{$this->name}_operator",
get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header()), $this->get_operators());
$mform->setType("{$this->name}_operator", PARAM_INT);
$mform->setDefault("{$this->name}_operator", self::ANY_VALUE);
// Value selector.
$elements[] = $mform->createElement('text', "{$this->name}_value1",
get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()), ['size' => 4]);
$mform->setType("{$this->name}_value1", PARAM_FLOAT);
$mform->setDefault("{$this->name}_value1", 1);
$mform->hideIf("{$this->name}_value1", "{$this->name}_operator", 'eq', self::ANY_VALUE);
// Unit selector.
$units = [
self::SIZE_UNIT_BYTE => new lang_string('sizeb'),
self::SIZE_UNIT_KILOBYTE => new lang_string('sizekb'),
self::SIZE_UNIT_MEGABYTE => new lang_string('sizemb'),
self::SIZE_UNIT_GIGABYTE => new lang_string('sizegb'),
];
$elements[] = $mform->createElement('select', "{$this->name}_unit",
get_string('filterfieldunit', 'core_reportbuilder', $this->get_header()), $units);
$mform->setType("{$this->name}_unit", PARAM_INT);
$mform->setDefault("{$this->name}_unit", self::SIZE_UNIT_BYTE);
$mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::ANY_VALUE);
$mform->addGroup($elements, "{$this->name}_group", $this->get_header(), '', false)
->setHiddenLabel(true);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
$operator = (int) ($values["{$this->name}_operator"] ?? self::ANY_VALUE);
$filesizevalue = unformat_float($values["{$this->name}_value1"] ?? 1);
$filesizeunit = (int) ($values["{$this->name}_unit"] ?? self::SIZE_UNIT_BYTE);
$paramfilesize = database::generate_param_name();
$params[$paramfilesize] = $filesizevalue * $filesizeunit;
switch ($operator) {
case self::LESS_THAN:
$sql = "{$fieldsql} < :{$paramfilesize}";
break;
case self::GREATER_THAN:
$sql = "{$fieldsql} > :{$paramfilesize}";
break;
default:
// Invalid or inactive filter.
return ['', []];
}
return [$sql, $params];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_operator" => self::GREATER_THAN,
"{$this->name}_value1" => 1,
"{$this->name}_unit" => self::SIZE_UNIT_KILOBYTE,
];
}
}
@@ -0,0 +1,213 @@
<?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\filters;
use core_reportbuilder\local\helpers\database;
/**
* Number report filter
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class number extends base {
/** @var int Any value */
public const ANY_VALUE = 0;
/** @var int Is not empty */
public const IS_NOT_EMPTY = 1;
/** @var int Is empty */
public const IS_EMPTY = 2;
/** @var int Less than */
public const LESS_THAN = 3;
/** @var int Greater than */
public const GREATER_THAN = 4;
/** @var int Equal to */
public const EQUAL_TO = 5;
/** @var int Equal or less than */
public const EQUAL_OR_LESS_THAN = 6;
/** @var int Equal or greater than */
public const EQUAL_OR_GREATER_THAN = 7;
/** @var int Range */
public const RANGE = 8;
/**
* Returns an array of comparison operators
*
* @return array of comparison operators
*/
private function get_operators(): array {
$operators = [
self::ANY_VALUE => get_string('filterisanyvalue', 'core_reportbuilder'),
self::IS_NOT_EMPTY => get_string('filterisnotempty', 'core_reportbuilder'),
self::IS_EMPTY => get_string('filterisempty', 'core_reportbuilder'),
self::LESS_THAN => get_string('filterlessthan', 'core_reportbuilder'),
self::GREATER_THAN => get_string('filtergreaterthan', 'core_reportbuilder'),
self::EQUAL_TO => get_string('filterisequalto', 'core_reportbuilder'),
self::EQUAL_OR_LESS_THAN => get_string('filterequalorlessthan', 'core_reportbuilder'),
self::EQUAL_OR_GREATER_THAN => get_string('filterequalorgreaterthan', 'core_reportbuilder'),
self::RANGE => get_string('filterrange', 'core_reportbuilder'),
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Adds controls specific to this filter in the form.
*
* @param \MoodleQuickForm $mform
*/
public function setup_form(\MoodleQuickForm $mform): void {
$objs = [];
$objs['select'] = $mform->createElement('select', $this->name . '_operator',
get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header()), $this->get_operators());
$mform->setType($this->name . '_operator', PARAM_INT);
$objs['text'] = $mform->createElement('text', $this->name . '_value1',
get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()), ['size' => 3]);
$mform->setType($this->name . '_value1', PARAM_INT);
$mform->setDefault($this->name . '_value1', 0);
$mform->hideIf($this->name . '_value1', $this->name . '_operator', 'in',
[self::ANY_VALUE, self::IS_NOT_EMPTY, self::IS_EMPTY]);
$objs['text2'] = $mform->createElement('text', $this->name . '_value2',
get_string('filterfieldto', 'reportbuilder', $this->get_header()), ['size' => 3]);
$mform->setType($this->name . '_value2', PARAM_INT);
$mform->setDefault($this->name . '_value2', 0);
$mform->hideIf($this->name . '_value2', $this->name . '_operator', 'noteq', self::RANGE);
$mform->addGroup($objs, $this->name . '_grp', $this->get_header(), '', false)
->setHiddenLabel(true);
}
/**
* Return filter SQL
*
* @param array $values
* @return array array of two elements - SQL query and named parameters
*/
public function get_sql_filter(array $values): array {
$operator = (int) ($values["{$this->name}_operator"] ?? self::ANY_VALUE);
$value1 = $values["{$this->name}_value1"] ?? null;
$value2 = $values["{$this->name}_value2"] ?? null;
// Validate filter form values.
if (!$this->validate_filter_values($operator, $value1, $value2)) {
// Filter configuration is invalid. Ignore the filter.
return ['', []];
}
[$param, $param2] = database::generate_param_names(2);
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
switch ($operator) {
case self::ANY_VALUE:
return ['', []];
case self::IS_NOT_EMPTY:
$res = "COALESCE({$fieldsql}, 0) <> 0";
break;
case self::IS_EMPTY:
$res = "COALESCE({$fieldsql}, 0) = 0";
break;
case self::LESS_THAN:
$res = "{$fieldsql} < :{$param}";
$params[$param] = $value1;
break;
case self::GREATER_THAN:
$res = "{$fieldsql} > :{$param}";
$params[$param] = $value1;
break;
case self::EQUAL_TO:
$res = "{$fieldsql} = :{$param}";
$params[$param] = $value1;
break;
case self::EQUAL_OR_LESS_THAN:
$res = "{$fieldsql} <= :{$param}";
$params[$param] = $value1;
break;
case self::EQUAL_OR_GREATER_THAN:
$res = "{$fieldsql} >= :{$param}";
$params[$param] = $value1;
break;
case self::RANGE:
$res = "{$fieldsql} BETWEEN :{$param} AND :{$param2}";
$params[$param] = $value1;
$params[$param2] = $value2;
break;
default:
// Filter configuration is invalid. Ignore the filter.
return ['', []];
}
return [$res, $params];
}
/**
* Validate filter form values
*
* @param int $operator
* @param int|null $value1
* @param int|null $value2
* @return bool
*/
private function validate_filter_values(int $operator, ?int $value1, ?int $value2): bool {
// Check that for any of these operators value1 can not be null.
$requirescomparisonvalue = [
self::LESS_THAN,
self::GREATER_THAN,
self::EQUAL_TO,
self::EQUAL_OR_LESS_THAN,
self::EQUAL_OR_GREATER_THAN
];
if (in_array($operator, $requirescomparisonvalue) && $value1 === null) {
return false;
}
// When operator is between $value1 and $value2, can not be null.
if (($operator === self::RANGE) && ($value1 === null || $value2 === null)) {
return false;
}
return true;
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_operator" => self::GREATER_THAN,
"{$this->name}_value1" => 1,
];
}
}
@@ -0,0 +1,153 @@
<?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\filters;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
/**
* Select report filter
*
* The options for the select are defined when creating the filter by calling {@see set_options} or {@see set_options_callback}
*
* To extend this class in your own filter (e.g. to pre-populate available options), you should override the {@see get_operators}
* and/or {@see get_select_options} methods
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class select extends base {
/** @var int Any value */
public const ANY_VALUE = 0;
/** @var int Equal to */
public const EQUAL_TO = 1;
/** @var int Not equal to */
public const NOT_EQUAL_TO = 2;
/**
* Returns an array of comparison operators
*
* @return array
*/
protected function get_operators(): array {
$operators = [
self::ANY_VALUE => get_string('filterisanyvalue', 'core_reportbuilder'),
self::EQUAL_TO => get_string('filterisequalto', 'core_reportbuilder'),
self::NOT_EQUAL_TO => get_string('filterisnotequalto', 'core_reportbuilder')
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Return the options for the filter as an array, to be used to populate the select input field
*
* @return array
*/
protected function get_select_options(): array {
return (array) $this->filter->get_options();
}
/**
* Adds controls specific to this filter in the form.
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$elements = [];
$elements['operator'] = $mform->createElement('select', $this->name . '_operator',
get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header()), $this->get_operators());
// If a multi-dimensional array is passed, we need to use a different element type.
$options = $this->get_select_options();
$element = (count($options) == count($options, COUNT_RECURSIVE) ? 'select' : 'selectgroups');
$elements['value'] = $mform->createElement($element, $this->name . '_value',
get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()), $options);
$mform->addGroup($elements, $this->name . '_group', $this->get_header(), '', false)
->setHiddenLabel(true);
$mform->hideIf($this->name . '_value', $this->name . '_operator', 'eq', self::ANY_VALUE);
}
/**
* Return filter SQL
*
* Note that operators must be of type integer, while values can be integer or string.
*
* @param array $values
* @return array array of two elements - SQL query and named parameters
*/
public function get_sql_filter(array $values): array {
$name = database::generate_param_name();
$operator = $values["{$this->name}_operator"] ?? self::ANY_VALUE;
$value = $values["{$this->name}_value"] ?? 0;
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
// Validate filter form values.
if (!$this->validate_filter_values((int) $operator, $value)) {
// Filter configuration is invalid. Ignore the filter.
return ['', []];
}
switch ($operator) {
case self::EQUAL_TO:
$fieldsql .= "=:$name";
$params[$name] = $value;
break;
case self::NOT_EQUAL_TO:
$fieldsql .= "<>:$name";
$params[$name] = $value;
break;
default:
return ['', []];
}
return [$fieldsql, $params];
}
/**
* Validate filter form values
*
* @param int|null $operator
* @param mixed|null $value
* @return bool
*/
private function validate_filter_values(?int $operator, $value): bool {
return !($operator === null || $value === '');
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_operator" => self::EQUAL_TO,
"{$this->name}_value" => 1,
];
}
}
@@ -0,0 +1,208 @@
<?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\filters;
use core_tag_tag;
use lang_string;
use MoodleQuickForm;
use stdClass;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\report\filter;
/**
* Class containing logic for the tags filter
*
* The filter can operate in two modes:
*
* 1. Filtering of tags directly from the {tag} table, in which case the field SQL expression should return the ID of that table;
* 2. Filtering of component tags, in which case the field SQL expression should return the ID of the component table that would
* join to the {tag_instance} itemid field
*
* If filtering component tags then the following must be passed to the {@see filter::get_options} method when using this filter
* in a report: ['component' => 'mycomponent', 'itemtype' => 'myitem']
*
* @package core_reportbuilder
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tags extends base {
/** @var int Any value */
public const ANY_VALUE = 0;
/** @var int Tags are present */
public const NOT_EMPTY = 1;
/** @var int Filter for selected tags */
public const EQUAL_TO = 2;
/** @var int Tags are not present */
public const EMPTY = 3;
/** @var int Filter for excluded tags */
public const NOT_EQUAL_TO = 4;
/**
* Returns an array of comparison operators
*
* @return array
*/
private function get_operators(): array {
$operators = [
self::ANY_VALUE => new lang_string('filterisanyvalue', 'core_reportbuilder'),
self::NOT_EMPTY => new lang_string('filterisnotempty', 'core_reportbuilder'),
self::EMPTY => new lang_string('filterisempty', 'core_reportbuilder'),
self::EQUAL_TO => new lang_string('filterisequalto', 'core_reportbuilder'),
self::NOT_EQUAL_TO => new lang_string('filterisnotequalto', 'core_reportbuilder'),
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
global $DB;
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
$mform->addElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators())
->setHiddenLabel(true);
// If we're filtering component tags, show only those related to the component itself.
$options = (array) $this->filter->get_options();
if (array_key_exists('component', $options) && array_key_exists('itemtype', $options)) {
$taginstancejoin = 'JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.component = :component AND ti.itemtype = :itemtype';
$params = array_intersect_key($options, array_flip(['component', 'itemtype']));
} else {
$taginstancejoin = '';
$params = [];
}
$sql = "SELECT DISTINCT t.id, t.name, t.rawname
FROM {tag} t
{$taginstancejoin}
ORDER BY t.name";
// Transform tag records into appropriate display name, for selection in the autocomplete element.
$tags = array_map(static function(stdClass $record): string {
return core_tag_tag::make_display_name($record);
}, $DB->get_records_sql($sql, $params));
$valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());
$mform->addElement('autocomplete', "{$this->name}_value", $valuelabel, $tags, ['multiple' => true])
->setHiddenLabel(true);
$mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'in', [self::ANY_VALUE, self::EMPTY, self::NOT_EMPTY]);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
global $DB;
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
$operator = (int) ($values["{$this->name}_operator"] ?? self::ANY_VALUE);
$tags = (array) ($values["{$this->name}_value"] ?? []);
// If we're filtering component tags, we need to perform [not] exists queries to ensure no row duplication occurs.
$options = (array) $this->filter->get_options();
if (array_key_exists('component', $options) && array_key_exists('itemtype', $options)) {
[$paramcomponent, $paramitemtype] = database::generate_param_names(2);
$componenttagselect = <<<EOF
SELECT 1
FROM {tag} t
JOIN {tag_instance} ti ON ti.tagid = t.id
WHERE ti.component = :{$paramcomponent} AND ti.itemtype = :{$paramitemtype} AND ti.itemid = {$fieldsql}
EOF;
$params[$paramcomponent] = $options['component'];
$params[$paramitemtype] = $options['itemtype'];
if ($operator === self::NOT_EMPTY) {
$select = "EXISTS ({$componenttagselect})";
} else if ($operator === self::EMPTY) {
$select = "NOT EXISTS ({$componenttagselect})";
} else if ($operator === self::EQUAL_TO && !empty($tags)) {
[$tagselect, $tagselectparams] = $DB->get_in_or_equal($tags, SQL_PARAMS_NAMED,
database::generate_param_name('_'));
$select = "EXISTS ({$componenttagselect} AND t.id {$tagselect})";
$params = array_merge($params, $tagselectparams);
} else if ($operator === self::NOT_EQUAL_TO && !empty($tags)) {
[$tagselect, $tagselectparams] = $DB->get_in_or_equal($tags, SQL_PARAMS_NAMED,
database::generate_param_name('_'));
// We should also return those elements that aren't tagged at all.
$select = "NOT EXISTS ({$componenttagselect} AND t.id {$tagselect})";
$params = array_merge($params, $tagselectparams);
} else {
// Invalid/inactive (any value) filter..
return ['', []];
}
} else {
// We're filtering directly from the tag table.
if ($operator === self::NOT_EMPTY) {
$select = "{$fieldsql} IS NOT NULL";
} else if ($operator === self::EMPTY) {
$select = "{$fieldsql} IS NULL";
} else if ($operator === self::EQUAL_TO && !empty($tags)) {
[$tagselect, $tagselectparams] = $DB->get_in_or_equal($tags, SQL_PARAMS_NAMED,
database::generate_param_name('_'));
$select = "{$fieldsql} {$tagselect}";
$params = array_merge($params, $tagselectparams);
} else if ($operator === self::NOT_EQUAL_TO && !empty($tags)) {
[$tagselect, $tagselectparams] = $DB->get_in_or_equal($tags, SQL_PARAMS_NAMED,
database::generate_param_name('_'), false);
// We should also return those elements that aren't tagged at all.
$select = "COALESCE({$fieldsql}, 0) {$tagselect}";
$params = array_merge($params, $tagselectparams);
} else {
// Invalid/inactive (any value) filter..
return ['', []];
}
}
return [$select, $params];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_operator" => self::EQUAL_TO,
"{$this->name}_value" => [1],
];
}
}
@@ -0,0 +1,204 @@
<?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\filters;
use core_reportbuilder\local\helpers\database;
/**
* Text report filter
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class text extends base {
/** @var int */
public const ANY_VALUE = 0;
/** @var int */
public const CONTAINS = 1;
/** @var int */
public const DOES_NOT_CONTAIN = 2;
/** @var int */
public const IS_EQUAL_TO = 3;
/** @var int */
public const IS_NOT_EQUAL_TO = 4;
/** @var int */
public const STARTS_WITH = 5;
/** @var int */
public const ENDS_WITH = 6;
/** @var int */
public const IS_EMPTY = 7;
/** @var int */
public const IS_NOT_EMPTY = 8;
/**
* Return an array of operators available for this filter
*
* @return array of comparison operators
*/
private function get_operators(): array {
$operators = [
self::ANY_VALUE => get_string('filterisanyvalue', 'core_reportbuilder'),
self::CONTAINS => get_string('filtercontains', 'core_reportbuilder'),
self::DOES_NOT_CONTAIN => get_string('filterdoesnotcontain', 'core_reportbuilder'),
self::IS_EQUAL_TO => get_string('filterisequalto', 'core_reportbuilder'),
self::IS_NOT_EQUAL_TO => get_string('filterisnotequalto', 'core_reportbuilder'),
self::STARTS_WITH => get_string('filterstartswith', 'core_reportbuilder'),
self::ENDS_WITH => get_string('filterendswith', 'core_reportbuilder'),
self::IS_EMPTY => get_string('filterisempty', 'core_reportbuilder'),
self::IS_NOT_EMPTY => get_string('filterisnotempty', 'core_reportbuilder')
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Adds controls specific to this filter in the form.
*
* Operator selector use the "$this->name . '_operator'" naming convention and the fields to enter custom values should
* use "$this->name . '_value'" or _value1/_value2/... in case there is more than one field for their naming.
*
* @param \MoodleQuickForm $mform
*/
public function setup_form(\MoodleQuickForm $mform): void {
$elements = [];
$elements['operator'] = $mform->createElement('select', $this->name . '_operator',
get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header()), $this->get_operators());
$elements['value'] = $mform->createElement('text', $this->name . '_value',
get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()));
$mform->addGroup($elements, $this->name . '_group', $this->get_header(), '', false)
->setHiddenLabel(true);
$mform->setType($this->name . '_value', PARAM_RAW_TRIMMED);
$mform->hideIf($this->name . '_value', $this->name . '_operator', 'eq', self::ANY_VALUE);
$mform->hideIf($this->name . '_value', $this->name . '_operator', 'eq', self::IS_EMPTY);
$mform->hideIf($this->name . '_value', $this->name . '_operator', 'eq', self::IS_NOT_EMPTY);
}
/**
* Return filter SQL
*
* @param array $values
* @return array array of two elements - SQL query and named parameters
*/
public function get_sql_filter(array $values): array {
global $DB;
$name = database::generate_param_name();
$operator = (int) ($values["{$this->name}_operator"] ?? self::ANY_VALUE);
$value = trim($values["{$this->name}_value"] ?? '');
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
// Validate filter form values.
if (!$this->validate_filter_values($operator, $value)) {
// Filter configuration is invalid. Ignore the filter.
return ['', []];
}
switch($operator) {
case self::CONTAINS:
$res = $DB->sql_like($fieldsql, ":$name", false, false);
$value = $DB->sql_like_escape($value);
$params[$name] = "%$value%";
break;
case self::DOES_NOT_CONTAIN:
$res = $DB->sql_like($fieldsql, ":$name", false, false, true);
$value = $DB->sql_like_escape($value);
$params[$name] = "%$value%";
break;
case self::IS_EQUAL_TO:
$res = $DB->sql_equal($fieldsql, ":$name", false, false);
$params[$name] = $value;
break;
case self::IS_NOT_EQUAL_TO:
$res = $DB->sql_equal($fieldsql, ":$name", false, false, true);
$params[$name] = $value;
break;
case self::STARTS_WITH:
$res = $DB->sql_like($fieldsql, ":$name", false, false);
$value = $DB->sql_like_escape($value);
$params[$name] = "$value%";
break;
case self::ENDS_WITH:
$res = $DB->sql_like($fieldsql, ":$name", false, false);
$value = $DB->sql_like_escape($value);
$params[$name] = "%$value";
break;
case self::IS_EMPTY:
$paramempty = database::generate_param_name();
$res = "COALESCE({$fieldsql}, :{$paramempty}) = :{$name}";
$params[$paramempty] = $params[$name] = '';
break;
case self::IS_NOT_EMPTY:
$paramempty = database::generate_param_name();
$res = "COALESCE({$fieldsql}, :{$paramempty}) != :{$name}";
$params[$paramempty] = $params[$name] = '';
break;
default:
// Filter configuration is invalid. Ignore the filter.
return ['', []];
}
return array($res, $params);
}
/**
* Validate filter form values
*
* @param int $operator
* @param string|null $value
* @return bool
*/
private function validate_filter_values(int $operator, ?string $value): bool {
$operatorsthatdontrequirevalue = [
self::ANY_VALUE,
self::IS_EMPTY,
self::IS_NOT_EMPTY,
];
if ($value === '' && !in_array($operator, $operatorsthatdontrequirevalue)) {
return false;
}
return true;
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_operator" => self::IS_EQUAL_TO,
"{$this->name}_value" => 'test',
];
}
}
@@ -0,0 +1,140 @@
<?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\filters;
use context_system;
use core_user;
use lang_string;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;
/**
* User report filter
*
* This filter expects field SQL referring to a user ID (e.g. "{$tableuser}.id")
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user extends base {
/** @var int Filter for any user */
public const USER_ANY = 0;
/** @var int Filter for current user */
public const USER_CURRENT = 1;
/** @var int Filter for selected user */
public const USER_SELECT = 2;
/**
* Return an array of operators available for this filter
*
* @return lang_string[]
*/
private function get_operators(): array {
$operators = [
self::USER_ANY => new lang_string('userany', 'core_reportbuilder'),
self::USER_CURRENT => new lang_string('usercurrent', 'core_reportbuilder'),
self::USER_SELECT => new lang_string('select'),
];
return $this->filter->restrict_limited_operators($operators);
}
/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
$mform->addElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators())
->setHiddenLabel(true);
$mform->setType("{$this->name}_operator", PARAM_INT);
$mform->setDefault("{$this->name}_operator", self::USER_ANY);
$options = [
'ajax' => 'core_user/form_user_selector',
'multiple' => true,
'valuehtmlcallback' => static function($userid): string {
$user = core_user::get_user($userid);
return fullname($user, has_capability('moodle/site:viewfullnames', context_system::instance()));
}
];
$mform->addElement('autocomplete', "{$this->name}_value", get_string('user'), [], $options)
->setHiddenLabel(true);
$mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'neq', self::USER_SELECT);
}
/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
global $DB, $USER;
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();
$operator = $values["{$this->name}_operator"] ?? self::USER_ANY;
$userids = $values["{$this->name}_value"] ?? [];
switch ($operator) {
case self::USER_CURRENT:
$paramuserid = database::generate_param_name();
$sql = "{$fieldsql} = :{$paramuserid}";
$params[$paramuserid] = $USER->id;
break;
case self::USER_SELECT:
[$useridselect, $useridparams] = $DB->get_in_or_equal(
$userids,
SQL_PARAMS_NAMED,
database::generate_param_name('_'),
true,
null,
);
$sql = "{$fieldsql} {$useridselect}";
$params = array_merge($params, $useridparams);
break;
default:
// Invalid or inactive filter.
return ['', []];
}
return [$sql, $params];
}
/**
* Return sample filter values
*
* @return array
*/
public function get_sample_values(): array {
return [
"{$this->name}_operator" => self::USER_SELECT,
"{$this->name}_value" => [1],
];
}
}
@@ -0,0 +1,89 @@
<?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_collator;
use core_component;
use core_reportbuilder\local\aggregation\base;
/**
* Helper class for column aggregation related methods
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class aggregation {
/**
* Helper method to convert aggregation class name into fully qualified namespaced class
*
* @param string $aggregation
* @return string
*/
public static function get_full_classpath(string $aggregation): string {
return "\\core_reportbuilder\\local\\aggregation\\{$aggregation}";
}
/**
* Validate whether given class is a valid aggregation type
*
* @param string $aggregationclass Fully qualified namespaced class, see {@see get_full_classpath} for converting value
* stored in column persistent to full path
* @return bool
*/
public static function valid(string $aggregationclass): bool {
return class_exists($aggregationclass) && is_subclass_of($aggregationclass, base::class);
}
/**
* Return list of all available/valid aggregation types
*
* @return base[]
*/
public static function get_aggregations(): array {
$classes = core_component::get_component_classes_in_namespace('core_reportbuilder', 'local\\aggregation');
return array_filter(array_keys($classes), static function(string $class): bool {
return static::valid($class);
});
}
/**
* Get available aggregation types for given column type
*
* @param int $columntype
* @param array $exclude List of types to exclude, e.g. ['min', 'sum']
* @return string[] Aggregation types indexed by [shortname => name]
*/
public static function get_column_aggregations(int $columntype, array $exclude = []): array {
$types = [];
$classes = static::get_aggregations();
foreach ($classes as $class) {
if ($class::compatible($columntype) && !in_array($class::get_class_name(), $exclude)) {
$types[$class::get_class_name()] = (string) $class::get_name();
}
}
core_collator::asort($types, core_collator::SORT_STRING);
return $types;
}
}
@@ -0,0 +1,320 @@
<?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 cache;
use context;
use context_system;
use core_collator;
use core_component;
use core_reportbuilder\local\audiences\base;
use core_reportbuilder\local\models\{audience as audience_model, schedule};
/**
* Class containing report audience helper methods
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class audience {
/**
* Return audience instances for a given report. Note that any records pointing to invalid audience types will be excluded
*
* @param int $reportid
* @return base[]
*/
public static function get_base_records(int $reportid): array {
$records = audience_model::get_records(['reportid' => $reportid], 'id');
$instances = array_map(static function(audience_model $audience): ?base {
return base::instance(0, $audience->to_record());
}, $records);
// Filter and remove null elements (invalid audience types).
return array_filter($instances);
}
/**
* Returns list of report IDs that the specified user can access, based on audience configuration. This can be expensive if the
* site has lots of reports, with lots of audiences, so we cache the result for the duration of the users session
*
* @param int|null $userid User ID to check, or the current user if omitted
* @return int[]
*/
public static function get_allowed_reports(?int $userid = null): array {
global $USER, $DB;
$userid = $userid ?: (int) $USER->id;
// Prepare cache, if we previously stored the users allowed reports then return that.
$cache = cache::make('core', 'reportbuilder_allowed_reports');
$cachedreports = $cache->get($userid);
if ($cachedreports !== false) {
return $cachedreports;
}
$allowedreports = [];
$reportaudiences = [];
// Retrieve all audiences and group them by report for convenience.
$audiences = audience_model::get_records();
foreach ($audiences as $audience) {
$reportaudiences[$audience->get('reportid')][] = $audience;
}
foreach ($reportaudiences as $reportid => $audiences) {
// Generate audience SQL based on those for the current report.
[$wheres, $params] = self::user_audience_sql($audiences);
if (count($wheres) === 0) {
continue;
}
$paramuserid = database::generate_param_name();
$params[$paramuserid] = $userid;
$sql = "SELECT DISTINCT(u.id)
FROM {user} u
WHERE (" . implode(' OR ', $wheres) . ")
AND u.deleted = 0
AND u.id = :{$paramuserid}";
// If we have a matching record, user can view the report.
if ($DB->record_exists_sql($sql, $params)) {
$allowedreports[] = $reportid;
}
}
// Store users allowed reports in cache.
$cache->set($userid, $allowedreports);
return $allowedreports;
}
/**
* Purge the audience cache of allowed reports
*/
public static function purge_caches(): void {
cache::make('core', 'reportbuilder_allowed_reports')->purge();
}
/**
* Generate SQL select clause and params for selecting reports specified user can access, based on audience configuration
*
* @param string $reporttablealias
* @param int|null $userid User ID to check, or the current user if omitted
* @return array
*/
public static function user_reports_list_sql(string $reporttablealias, ?int $userid = null): array {
global $DB;
$allowedreports = self::get_allowed_reports($userid);
if (empty($allowedreports)) {
return ['1=0', []];
}
// Get all sql audiences.
[$select, $params] = $DB->get_in_or_equal($allowedreports, SQL_PARAMS_NAMED, database::generate_param_name('_'));
$sql = "{$reporttablealias}.id {$select}";
return [$sql, $params];
}
/**
* Return list of report ID's specified user can access, based on audience configuration
*
* @param int|null $userid User ID to check, or the current user if omitted
* @return int[]
*/
public static function user_reports_list(?int $userid = null): array {
global $DB;
[$select, $params] = self::user_reports_list_sql('rb', $userid);
$sql = "SELECT rb.id
FROM {reportbuilder_report} rb
WHERE {$select}";
return $DB->get_fieldset_sql($sql, $params);
}
/**
* Returns SQL to limit the list of reports to those that the given user has access to
*
* - A user with 'viewall/editall' capability will have access to all reports
* - A user with 'edit' capability will have access to:
* - Those reports this user has created
* - Those reports this user is in audience of
* - Otherwise:
* - Those reports this user is in audience of
*
* @param string $reporttablealias
* @param int|null $userid User ID to check, or the current user if omitted
* @param context|null $context
* @return array
*/
public static function user_reports_list_access_sql(
string $reporttablealias,
?int $userid = null,
?context $context = null
): array {
global $DB, $USER;
if ($context === null) {
$context = context_system::instance();
}
if (has_any_capability(['moodle/reportbuilder:editall', 'moodle/reportbuilder:viewall'], $context, $userid)) {
return ['1=1', []];
}
// Limit the returned list to those reports the user can see, by selecting based on report audience.
[$reportselect, $params] = $DB->get_in_or_equal(
self::user_reports_list($userid),
SQL_PARAMS_NAMED,
database::generate_param_name('_'),
true,
null,
);
$where = "{$reporttablealias}.id {$reportselect}";
// User can also see any reports that they can edit.
if (has_capability('moodle/reportbuilder:edit', $context, $userid)) {
$paramuserid = database::generate_param_name();
$where = "({$reporttablealias}.usercreated = :{$paramuserid} OR {$where})";
$params[$paramuserid] = $userid ?? $USER->id;
}
return [$where, $params];
}
/**
* Return appropriate list of where clauses and params for given audiences
*
* @param audience_model[] $audiences
* @param string $usertablealias
* @return array[] [$wheres, $params]
*/
public static function user_audience_sql(array $audiences, string $usertablealias = 'u'): array {
$wheres = $params = [];
foreach ($audiences as $audience) {
if ($instance = base::instance(0, $audience->to_record())) {
$instancetablealias = database::generate_alias();
[$instancejoin, $instancewhere, $instanceparams] = $instance->get_sql($instancetablealias);
$wheres[] = "{$usertablealias}.id IN (
SELECT {$instancetablealias}.id
FROM {user} {$instancetablealias}
{$instancejoin}
WHERE {$instancewhere}
)";
$params += $instanceparams;
}
}
return [$wheres, $params];
}
/**
* Return a list of audiences that are used by any schedule of the given report
*
* @param int $reportid
* @return int[] Array of audience IDs
*/
public static function get_audiences_for_report_schedules(int $reportid): array {
global $DB;
$audiences = $DB->get_fieldset_select(schedule::TABLE, 'audiences', 'reportid = ?', [$reportid]);
// Reduce JSON encoded audience data of each schedule to an array of audience IDs.
$audienceids = array_reduce($audiences, static function(array $carry, string $audience): array {
return array_merge($carry, (array) json_decode($audience));
}, []);
return array_unique($audienceids, SORT_NUMERIC);
}
/**
* Returns the list of audiences types in the system.
*
* @return array
*/
private static function get_audience_types(): array {
$sources = [];
$audiences = core_component::get_component_classes_in_namespace(null, 'reportbuilder\\audience');
foreach ($audiences as $class => $path) {
$audienceclass = $class::instance();
if (is_subclass_of($class, base::class) && $audienceclass->user_can_add()) {
$componentname = $audienceclass->get_component_displayname();
$sources[$componentname][$class] = $audienceclass->get_name();
}
}
return $sources;
}
/**
* Get all the audiences types the current user can add to, organised by categories.
*
* @return array
*
* @deprecated since Moodle 4.1 - please do not use this function any more, {@see custom_report_audience_cards_exporter}
*/
public static function get_all_audiences_menu_types(): array {
debugging('The function ' . __FUNCTION__ . '() is deprecated, please do not use it any more. ' .
'See \'custom_report_audience_cards_exporter\' class for replacement', DEBUG_DEVELOPER);
$menucardsarray = [];
$notavailablestr = get_string('notavailable', 'moodle');
$audiencetypes = self::get_audience_types();
$audiencetypeindex = 0;
foreach ($audiencetypes as $categoryname => $audience) {
$menucards = [
'name' => $categoryname,
'key' => 'index' . ++$audiencetypeindex,
];
foreach ($audience as $classname => $name) {
$class = $classname::instance();
$title = $class->is_available() ? get_string('addaudience', 'core_reportbuilder', $class->get_name()) :
$notavailablestr;
$menucard['title'] = $title;
$menucard['name'] = $class->get_name();
$menucard['disabled'] = !$class->is_available();
$menucard['identifier'] = get_class($class);
$menucard['action'] = 'add-audience';
$menucards['items'][] = $menucard;
}
// Order audience types on each category alphabetically.
core_collator::asort_array_of_arrays_by_key($menucards['items'], 'name');
$menucards['items'] = array_values($menucards['items']);
$menucardsarray[] = $menucards;
}
return $menucardsarray;
}
}
@@ -0,0 +1,319 @@
<?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, {$customdatatablealias}.valuetrust";
}
$columns[] = (new column(
'customfield_' . $field->get('shortname'),
new lang_string('customfieldcolumn', 'core_reportbuilder', $field->get_formatted_name(false)),
$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(false)),
$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 {
return $field->get_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;
}
}
@@ -0,0 +1,188 @@
<?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 coding_exception;
use core_text;
/**
* Helper functions for DB manipulations
*
* @package core_reportbuilder
* @copyright 2019 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class database {
/** @var string Prefix for generated aliases */
private const GENERATE_ALIAS_PREFIX = 'rbalias';
/** @var string Prefix for generated param names names */
private const GENERATE_PARAM_PREFIX = 'rbparam';
/**
* Generates unique table/column alias that must be used in generated SQL
*
* @param string $suffix Optional string to append to alias
* @return string
*/
public static function generate_alias(string $suffix = ''): string {
static $aliascount = 0;
return static::GENERATE_ALIAS_PREFIX . ($aliascount++) . $suffix;
}
/**
* Generate multiple unique table/column aliases, see {@see generate_alias} for info
*
* @param int $count
* @param string $suffix
* @return string[]
*/
public static function generate_aliases(int $count, string $suffix = ''): array {
return array_map([static::class, 'generate_alias'], array_fill(0, $count, $suffix));
}
/**
* Generates unique parameter name that must be used in generated SQL
*
* When passing the returned value to {@see \moodle_database::get_in_or_equal} it's recommended to define the suffix
*
* @param string $suffix Optional string to append to parameter name
* @return string
*/
public static function generate_param_name(string $suffix = ''): string {
static $paramcount = 0;
return static::GENERATE_PARAM_PREFIX . ($paramcount++) . $suffix;
}
/**
* Generate multiple unique parameter names, see {@see generate_param_name} for info
*
* @param int $count
* @param string $suffix
* @return string[]
*/
public static function generate_param_names(int $count, string $suffix = ''): array {
return array_map([static::class, 'generate_param_name'], array_fill(0, $count, $suffix));
}
/**
* Validate that parameter names were generated using {@see generate_param_name}.
*
* @param array $params
* @return bool
* @throws coding_exception For invalid params.
*/
public static function validate_params(array $params): bool {
$nonmatchingkeys = array_filter($params, static function($key): bool {
return !preg_match('/^' . static::GENERATE_PARAM_PREFIX . '[\d]+/', $key);
}, ARRAY_FILTER_USE_KEY);
if (!empty($nonmatchingkeys)) {
throw new coding_exception('Invalid parameter names', implode(', ', array_keys($nonmatchingkeys)));
}
return true;
}
/**
* Replace parameter names within given SQL expression, allowing caller to specify callback to handle their replacement
* primarily to ensure uniqueness when the expression is to be used as part of a larger query
*
* @param string $sql
* @param array $params Parameter names
* @param callable $callback Method that takes a single string parameter, and returns another string
* @return string
*/
public static function sql_replace_parameter_names(string $sql, array $params, callable $callback): string {
foreach ($params as $param) {
// Pattern to look for param within the SQL.
$pattern = '/:(?<param>' . preg_quote($param) . ')\b/';
$sql = preg_replace_callback($pattern, function(array $matches) use ($callback): string {
return ':' . $callback($matches['param']);
}, $sql);
}
return $sql;
}
/**
* Replace parameter names within given SQL expression, returning updated SQL and parameter elements
*
* {@see sql_replace_parameter_names}
*
* @param string $sql
* @param array $params Parameter name/values
* @param callable $callback
* @return array [$sql, $params]
*/
public static function sql_replace_parameters(string $sql, array $params, callable $callback): array {
$transformedsql = static::sql_replace_parameter_names($sql, array_keys($params), $callback);
$transformedparams = [];
foreach ($params as $name => $value) {
$transformedparams[$callback($name)] = $value;
}
return [$transformedsql, $transformedparams];
}
/**
* Generate SQL expression for sorting group concatenated fields
*
* @param string $field The original field or SQL expression
* @param string|null $sort A valid SQL ORDER BY to sort the concatenated fields, if omitted then $field will be used
* @return string
*/
public static function sql_group_concat_sort(string $field, string $sort = null): string {
global $DB;
// Fallback to sorting by the specified field, unless it contains parameters which would be duplicated.
if ($sort === null && !preg_match('/[:?$]/', $field)) {
$fieldsort = $field;
} else {
$fieldsort = $sort;
}
// Nothing to sort by.
if ($fieldsort === null) {
return '';
}
// If the sort specifies a direction, we need to handle that differently in Postgres.
if ($DB->get_dbfamily() === 'postgres') {
$fieldsortdirection = '';
preg_match('/(?<direction>ASC|DESC)?$/i', $fieldsort, $matches);
if (array_key_exists('direction', $matches)) {
$fieldsortdirection = $matches['direction'];
$fieldsort = core_text::substr($fieldsort, 0, -(core_text::strlen($fieldsortdirection)));
}
// Cast sort, stick the direction on the end.
$fieldsort = $DB->sql_cast_to_char($fieldsort) . ' ' . $fieldsortdirection;
}
return $fieldsort;
}
}
@@ -0,0 +1,69 @@
<?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 stdClass;
/**
* Class containing helper methods for formatting column data via callbacks
*
* @package core_reportbuilder
* @copyright 2021 Sara Arjona <sara@moodle.com> based on Alberto Lara Hernández <albertolara@moodle.com> code.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class format {
/**
* Returns formatted date.
*
* @param int|null $value Unix timestamp
* @param stdClass $row
* @param string|null $format Format string for strftime
* @return string
*/
public static function userdate(?int $value, stdClass $row, ?string $format = null): string {
return $value ? userdate($value, $format) : '';
}
/**
* Returns yes/no string depending on the given value
*
* @param bool|null $value
* @return string
*/
public static function boolean_as_text(?bool $value): string {
if ($value === null) {
return '';
}
return $value ? get_string('yes') : get_string('no');
}
/**
* Returns float value as a percentage
*
* @param float|null $value
* @return string
*/
public static function percent(?float $value): string {
if ($value === null) {
return '';
}
return get_string('percents', 'moodle', format_float($value));
}
}
@@ -0,0 +1,480 @@
<?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 stdClass;
use invalid_parameter_exception;
use core\persistent;
use core_reportbuilder\datasource;
use core_reportbuilder\manager;
use core_reportbuilder\local\models\column;
use core_reportbuilder\local\models\filter;
use core_reportbuilder\local\models\report as report_model;
use core_tag_tag;
/**
* Helper class for manipulating custom reports and their elements (columns, filters, conditions, etc)
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report {
/**
* Create custom report
*
* @param stdClass $data
* @param bool $default If $default is set to true it will populate report with default layout as defined by the selected
* source. These include pre-defined columns, filters and conditions.
* @return report_model
*/
public static function create_report(stdClass $data, bool $default = true): report_model {
$data->name = trim($data->name);
$data->type = datasource::TYPE_CUSTOM_REPORT;
// Create report persistent.
$report = manager::create_report_persistent($data);
// Add datasource default columns, filters and conditions to the report.
if ($default) {
$source = $report->get('source');
/** @var datasource $datasource */
$datasource = new $source($report);
$datasource->add_default_columns();
$datasource->add_default_filters();
$datasource->add_default_conditions();
}
// Report tags.
if (property_exists($data, "tags")) {
core_tag_tag::set_item_tags('core_reportbuilder', 'reportbuilder_report', $report->get('id'),
$report->get_context(), $data->tags);
}
return $report;
}
/**
* Update custom report
*
* @param stdClass $data
* @return report_model
*/
public static function update_report(stdClass $data): report_model {
$report = report_model::get_record(['id' => $data->id, 'type' => datasource::TYPE_CUSTOM_REPORT]);
if ($report === false) {
throw new invalid_parameter_exception('Invalid report');
}
$report->set_many([
'name' => trim($data->name),
'uniquerows' => $data->uniquerows,
])->update();
// Report tags.
if (property_exists($data, "tags")) {
core_tag_tag::set_item_tags('core_reportbuilder', 'reportbuilder_report', $report->get('id'),
$report->get_context(), $data->tags);
}
return $report;
}
/**
* Delete custom report
*
* @param int $reportid
* @return bool
* @throws invalid_parameter_exception
*/
public static function delete_report(int $reportid): bool {
$report = report_model::get_record(['id' => $reportid, 'type' => datasource::TYPE_CUSTOM_REPORT]);
if ($report === false) {
throw new invalid_parameter_exception('Invalid report');
}
// Report tags.
core_tag_tag::remove_all_item_tags('core_reportbuilder', 'reportbuilder_report', $report->get('id'));
return $report->delete();
}
/**
* Add given column to report
*
* @param int $reportid
* @param string $uniqueidentifier
* @return column
* @throws invalid_parameter_exception
*/
public static function add_report_column(int $reportid, string $uniqueidentifier): column {
$report = manager::get_report_from_id($reportid);
// Ensure column is available.
if (!array_key_exists($uniqueidentifier, $report->get_columns())) {
throw new invalid_parameter_exception('Invalid column');
}
$column = new column(0, (object) [
'reportid' => $reportid,
'uniqueidentifier' => $uniqueidentifier,
'columnorder' => column::get_max_columnorder($reportid, 'columnorder') + 1,
'sortorder' => column::get_max_columnorder($reportid, 'sortorder') + 1,
]);
return $column->create();
}
/**
* Delete given column from report
*
* @param int $reportid
* @param int $columnid
* @return bool
* @throws invalid_parameter_exception
*/
public static function delete_report_column(int $reportid, int $columnid): bool {
global $DB;
$column = column::get_record(['id' => $columnid, 'reportid' => $reportid]);
if ($column === false) {
throw new invalid_parameter_exception('Invalid column');
}
// After deletion, re-index remaining report columns.
if ($result = $column->delete()) {
$sqlupdateorder = '
UPDATE {' . column::TABLE . '}
SET columnorder = columnorder - 1
WHERE reportid = :reportid
AND columnorder > :columnorder';
$DB->execute($sqlupdateorder, ['reportid' => $reportid, 'columnorder' => $column->get('columnorder')]);
}
return $result;
}
/**
* Re-order given column within a report
*
* @param int $reportid
* @param int $columnid
* @param int $position
* @return bool
* @throws invalid_parameter_exception
*/
public static function reorder_report_column(int $reportid, int $columnid, int $position): bool {
$column = column::get_record(['id' => $columnid, 'reportid' => $reportid]);
if ($column === false) {
throw new invalid_parameter_exception('Invalid column');
}
// Get the rest of the report columns, excluding the one we are moving.
$columns = column::get_records_select('reportid = :reportid AND id <> :id', [
'reportid' => $reportid,
'id' => $columnid,
], 'columnorder');
return static::reorder_persistents_by_field($column, $columns, $position, 'columnorder');
}
/**
* Re-order given column sorting within a report
*
* @param int $reportid
* @param int $columnid
* @param int $position
* @return bool
* @throws invalid_parameter_exception
*/
public static function reorder_report_column_sorting(int $reportid, int $columnid, int $position): bool {
$column = column::get_record(['id' => $columnid, 'reportid' => $reportid]);
if ($column === false) {
throw new invalid_parameter_exception('Invalid column');
}
// Get the rest of the report columns, excluding the one we are moving.
$columns = column::get_records_select('reportid = :reportid AND id <> :id', [
'reportid' => $reportid,
'id' => $columnid,
], 'sortorder');
return static::reorder_persistents_by_field($column, $columns, $position, 'sortorder');
}
/**
* Toggle sorting options for given column within a report
*
* @param int $reportid
* @param int $columnid
* @param bool $enabled
* @param int $direction
* @return bool
* @throws invalid_parameter_exception
*/
public static function toggle_report_column_sorting(int $reportid, int $columnid, bool $enabled,
int $direction = SORT_ASC): bool {
$column = column::get_record(['id' => $columnid, 'reportid' => $reportid]);
if ($column === false) {
throw new invalid_parameter_exception('Invalid column');
}
return $column->set_many([
'sortenabled' => $enabled,
'sortdirection' => $direction,
])->update();
}
/**
* Add given condition to report
*
* @param int $reportid
* @param string $uniqueidentifier
* @return filter
* @throws invalid_parameter_exception
*/
public static function add_report_condition(int $reportid, string $uniqueidentifier): filter {
$report = manager::get_report_from_id($reportid);
// Ensure condition is available.
if (!array_key_exists($uniqueidentifier, $report->get_conditions())) {
throw new invalid_parameter_exception('Invalid condition');
}
// Ensure condition wasn't already added.
if (array_key_exists($uniqueidentifier, $report->get_active_conditions())) {
throw new invalid_parameter_exception('Duplicate condition');
}
$condition = new filter(0, (object) [
'reportid' => $reportid,
'uniqueidentifier' => $uniqueidentifier,
'iscondition' => true,
'filterorder' => filter::get_max_filterorder($reportid, true) + 1,
]);
return $condition->create();
}
/**
* Delete given condition from report
*
* @param int $reportid
* @param int $conditionid
* @return bool
* @throws invalid_parameter_exception
*/
public static function delete_report_condition(int $reportid, int $conditionid): bool {
global $DB;
$condition = filter::get_condition_record($reportid, $conditionid);
if ($condition === false) {
throw new invalid_parameter_exception('Invalid condition');
}
// After deletion, re-index remaining report conditions.
if ($result = $condition->delete()) {
$sqlupdateorder = '
UPDATE {' . filter::TABLE . '}
SET filterorder = filterorder - 1
WHERE reportid = :reportid
AND filterorder > :filterorder
AND iscondition = 1';
$DB->execute($sqlupdateorder, ['reportid' => $reportid, 'filterorder' => $condition->get('filterorder')]);
}
return $result;
}
/**
* Re-order given condition within a report
*
* @param int $reportid
* @param int $conditionid
* @param int $position
* @return bool
* @throws invalid_parameter_exception
*/
public static function reorder_report_condition(int $reportid, int $conditionid, int $position): bool {
$condition = filter::get_condition_record($reportid, $conditionid);
if ($condition === false) {
throw new invalid_parameter_exception('Invalid condition');
}
// Get the rest of the report conditions, excluding the one we are moving.
$conditions = filter::get_records_select('reportid = :reportid AND iscondition = 1 AND id <> :id', [
'reportid' => $reportid,
'id' => $conditionid,
], 'filterorder');
return static::reorder_persistents_by_field($condition, $conditions, $position, 'filterorder');
}
/**
* Add given filter to report
*
* @param int $reportid
* @param string $uniqueidentifier
* @return filter
* @throws invalid_parameter_exception
*/
public static function add_report_filter(int $reportid, string $uniqueidentifier): filter {
$report = manager::get_report_from_id($reportid);
// Ensure filter is available.
if (!array_key_exists($uniqueidentifier, $report->get_filters())) {
throw new invalid_parameter_exception('Invalid filter');
}
// Ensure filter wasn't already added.
if (array_key_exists($uniqueidentifier, $report->get_active_filters())) {
throw new invalid_parameter_exception('Duplicate filter');
}
$filter = new filter(0, (object) [
'reportid' => $reportid,
'uniqueidentifier' => $uniqueidentifier,
'filterorder' => filter::get_max_filterorder($reportid) + 1,
]);
return $filter->create();
}
/**
* Delete given filter from report
*
* @param int $reportid
* @param int $filterid
* @return bool
* @throws invalid_parameter_exception
*/
public static function delete_report_filter(int $reportid, int $filterid): bool {
global $DB;
$filter = filter::get_filter_record($reportid, $filterid);
if ($filter === false) {
throw new invalid_parameter_exception('Invalid filter');
}
// After deletion, re-index remaining report filters.
if ($result = $filter->delete()) {
$sqlupdateorder = '
UPDATE {' . filter::TABLE . '}
SET filterorder = filterorder - 1
WHERE reportid = :reportid
AND filterorder > :filterorder
AND iscondition = 0';
$DB->execute($sqlupdateorder, ['reportid' => $reportid, 'filterorder' => $filter->get('filterorder')]);
}
return $result;
}
/**
* Re-order given filter within a report
*
* @param int $reportid
* @param int $filterid
* @param int $position
* @return bool
* @throws invalid_parameter_exception
*/
public static function reorder_report_filter(int $reportid, int $filterid, int $position): bool {
$filter = filter::get_filter_record($reportid, $filterid);
if ($filter === false) {
throw new invalid_parameter_exception('Invalid filter');
}
// Get the rest of the report filters, excluding the one we are moving.
$filters = filter::get_records_select('reportid = :reportid AND iscondition = 0 AND id <> :id', [
'reportid' => $reportid,
'id' => $filterid,
], 'filterorder');
return static::reorder_persistents_by_field($filter, $filters, $position, 'filterorder');
}
/**
* Get available columns for a given report
*
* @param report_model $persistent
* @return array
*
* @deprecated since Moodle 4.1 - please do not use this function any more, {@see custom_report_column_cards_exporter}
*/
public static function get_available_columns(report_model $persistent): array {
debugging('The function ' . __FUNCTION__ . '() is deprecated, please do not use it any more. ' .
'See \'custom_report_column_cards_exporter\' class for replacement', DEBUG_DEVELOPER);
$available = [];
$report = manager::get_report_from_persistent($persistent);
// Get current report columns.
foreach ($report->get_columns() as $column) {
$entityname = $column->get_entity_name();
$entitytitle = $column->get_title();
if (!array_key_exists($entityname, $available)) {
$available[$entityname] = [
'name' => (string) $report->get_entity_title($entityname),
'key' => $entityname,
'items' => [],
];
}
$available[$entityname]['items'][] = [
'name' => $entitytitle,
'identifier' => $column->get_unique_identifier(),
'title' => get_string('addcolumn', 'core_reportbuilder', $entitytitle),
'action' => 'report-add-column'
];
}
return array_values($available);
}
/**
* Helper method for re-ordering given persistents (columns, filters, etc)
*
* @param persistent $persistent The persistent we are moving
* @param persistent[] $persistents The rest of the persistents
* @param int $position
* @param string $field The field we need to update
* @return bool
*/
private static function reorder_persistents_by_field(persistent $persistent, array $persistents, int $position,
string $field): bool {
// Splice into new position.
array_splice($persistents, $position - 1, 0, [$persistent]);
$fieldorder = 1;
foreach ($persistents as $persistent) {
$persistent->set($field, $fieldorder++)
->update();
}
return true;
}
}
@@ -0,0 +1,398 @@
<?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 context_user;
use core_user;
use invalid_parameter_exception;
use stdClass;
use stored_file;
use table_dataformat_export_format;
use core\message\message;
use core\plugininfo\dataformat;
use core_reportbuilder\local\models\audience as audience_model;
use core_reportbuilder\local\models\schedule as model;
use core_reportbuilder\table\custom_report_table_view;
/**
* Helper class for report schedule related methods
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class schedule {
/**
* Create report schedule, calculate when it should be next sent
*
* @param stdClass $data
* @param int|null $timenow Time to use as comparison against current date (defaults to current time)
* @return model
*/
public static function create_schedule(stdClass $data, ?int $timenow = null): model {
$data->name = trim($data->name);
$schedule = (new model(0, $data));
$schedule->set('timenextsend', self::calculate_next_send_time($schedule, $timenow));
return $schedule->create();
}
/**
* Update report schedule
*
* @param stdClass $data
* @return model
* @throws invalid_parameter_exception
*/
public static function update_schedule(stdClass $data): model {
$schedule = model::get_record(['id' => $data->id, 'reportid' => $data->reportid]);
if ($schedule === false) {
throw new invalid_parameter_exception('Invalid schedule');
}
// Normalize model properties.
$data = array_intersect_key((array) $data, model::properties_definition());
if (array_key_exists('name', $data)) {
$data['name'] = trim($data['name']);
}
$schedule->set_many($data);
$schedule->set('timenextsend', self::calculate_next_send_time($schedule))
->update();
return $schedule;
}
/**
* Toggle report schedule enabled
*
* @param int $reportid
* @param int $scheduleid
* @param bool $enabled
* @return bool
* @throws invalid_parameter_exception
*/
public static function toggle_schedule(int $reportid, int $scheduleid, bool $enabled): bool {
$schedule = model::get_record(['id' => $scheduleid, 'reportid' => $reportid]);
if ($schedule === false) {
throw new invalid_parameter_exception('Invalid schedule');
}
return $schedule->set('enabled', $enabled)->update();
}
/**
* Return array of users who match the audience records added to the given schedule
*
* @param model $schedule
* @return stdClass[]
*/
public static function get_schedule_report_users(model $schedule): array {
global $DB;
$audienceids = (array) json_decode($schedule->get('audiences'));
// Retrieve all selected audience records for the schedule.
[$audienceselect, $audienceparams] = $DB->get_in_or_equal($audienceids, SQL_PARAMS_NAMED, 'aid', true, null);
$audiences = audience_model::get_records_select("id {$audienceselect}", $audienceparams);
// Now convert audiences to SQL for user retrieval.
[$wheres, $params] = audience::user_audience_sql($audiences);
if (count($wheres) === 0) {
return [];
}
[$userorder] = users_order_by_sql('u');
$sql = 'SELECT u.*
FROM {user} u
WHERE (' . implode(' OR ', $wheres) . ')
AND u.deleted = 0
ORDER BY ' . $userorder;
return $DB->get_records_sql($sql, $params);
}
/**
* Return count of schedule report rows
*
* @param model $schedule
* @return int
*/
public static function get_schedule_report_count(model $schedule): int {
global $DB;
$table = custom_report_table_view::create($schedule->get('reportid'));
$table->setup();
return $DB->count_records_sql($table->countsql, $table->countparams);
}
/**
* Generate stored file instance for given schedule, in user draft
*
* @param model $schedule
* @return stored_file
*/
public static function get_schedule_report_file(model $schedule): stored_file {
global $CFG, $USER;
require_once("{$CFG->libdir}/filelib.php");
$table = custom_report_table_view::create($schedule->get('reportid'));
$table->setup();
$table->query_db(0, false);
// Set up table as if it were being downloaded, retrieve appropriate export class (ensure output buffer is
// cleaned in order to instantiate export class without exception).
ob_start();
$table->download = $schedule->get('format');
$exportclass = new table_dataformat_export_format($table, $table->download);
ob_end_clean();
// Create our schedule report stored file temporarily in user draft.
$filerecord = [
'contextid' => context_user::instance($USER->id)->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => file_get_unused_draft_itemid(),
'filepath' => '/',
'filename' => clean_filename($schedule->get_formatted_name()),
];
$storedfile = \core\dataformat::write_data_to_filearea(
$filerecord,
$table->download,
$exportclass->format_data($table->headers),
$table->rawdata,
static function(stdClass $record, bool $supportshtml) use ($table, $exportclass): array {
$record = $table->format_row($record);
if (!$supportshtml) {
$record = $exportclass->format_data($record);
}
return $record;
}
);
$table->close_recordset();
return $storedfile;
}
/**
* Check whether given schedule needs to be sent
*
* @param model $schedule
* @return bool
*/
public static function should_send_schedule(model $schedule): bool {
if (!$schedule->get('enabled')) {
return false;
}
$timenow = time();
// Ensure we've reached the initial scheduled start time.
$timescheduled = $schedule->get('timescheduled');
if ($timescheduled > $timenow) {
return false;
}
// If there's no recurrence, check whether it's been sent since initial scheduled start time. This ensures that even if
// the schedule was manually sent beforehand, it'll still be automatically sent once the start time is first reached.
if ($schedule->get('recurrence') === model::RECURRENCE_NONE) {
return $schedule->get('timelastsent') < $timescheduled;
}
return $schedule->get('timenextsend') <= $timenow;
}
/**
* Calculate the next time a schedule should be sent, based on it's recurrence and when it was initially scheduled. Ensures
* returned value is after the current date
*
* @param model $schedule
* @param int|null $timenow Time to use as comparison against current date (defaults to current time)
* @return int
*/
public static function calculate_next_send_time(model $schedule, ?int $timenow = null): int {
global $CFG;
$timenow = $timenow ?? time();
$recurrence = $schedule->get('recurrence');
$timescheduled = $schedule->get('timescheduled');
// If no recurrence is set or we haven't reached last sent date, return early.
if ($recurrence === model::RECURRENCE_NONE || $timescheduled > $timenow) {
return $timescheduled;
}
// Extract attributes from date (year, month, day, hours, minutes).
[
'year' => $year,
'mon' => $month,
'mday' => $day,
'wday' => $dayofweek,
'hours' => $hour,
'minutes' => $minute,
] = usergetdate($timescheduled, $CFG->timezone);
switch ($recurrence) {
case model::RECURRENCE_DAILY:
$day += 1;
break;
case model::RECURRENCE_WEEKDAYS:
$day += 1;
$calendar = \core_calendar\type_factory::get_calendar_instance();
$weekend = get_config('core', 'calendar_weekend');
// Increment day until day of week falls on a weekday.
while ((bool) ($weekend & (1 << (++$dayofweek % $calendar->get_num_weekdays())))) {
$day++;
}
break;
case model::RECURRENCE_WEEKLY:
$day += 7;
break;
case model::RECURRENCE_MONTHLY:
$month += 1;
break;
case model::RECURRENCE_ANNUALLY:
$year += 1;
break;
}
// We need to recursively increment the timestamp until we get one after the current time.
$timestamp = make_timestamp($year, $month, $day, $hour, $minute, 0, $CFG->timezone);
if ($timestamp < $timenow) {
// Ensure we don't modify anything in the original model.
$scheduleclone = new model(0, $schedule->to_record());
return self::calculate_next_send_time(
$scheduleclone->set('timescheduled', $timestamp), $timenow);
} else {
return $timestamp;
}
}
/**
* Send schedule message to user
*
* @param model $schedule
* @param stdClass $user
* @param stored_file $attachment
* @return bool
*/
public static function send_schedule_message(model $schedule, stdClass $user, stored_file $attachment): bool {
$message = new message();
$message->component = 'moodle';
$message->name = 'reportbuilderschedule';
$message->courseid = SITEID;
$message->userfrom = core_user::get_noreply_user();
$message->userto = $user;
$message->subject = $schedule->get('subject');
$message->fullmessage = $schedule->get('message');
$message->fullmessageformat = $schedule->get('messageformat');
$message->fullmessagehtml = $message->fullmessage;
$message->smallmessage = $message->fullmessage;
// Attach report to outgoing message.
$message->attachment = $attachment;
$message->attachname = $attachment->get_filename();
return (bool) message_send($message);
}
/**
* Delete report schedule
*
* @param int $reportid
* @param int $scheduleid
* @return bool
* @throws invalid_parameter_exception
*/
public static function delete_schedule(int $reportid, int $scheduleid): bool {
$schedule = model::get_record(['id' => $scheduleid, 'reportid' => $reportid]);
if ($schedule === false) {
throw new invalid_parameter_exception('Invalid schedule');
}
return $schedule->delete();
}
/**
* Return list of available data formats
*
* @return string[]
*/
public static function get_format_options(): array {
$dataformats = dataformat::get_enabled_plugins();
return array_map(static function(string $pluginname): string {
return get_string('dataformat', 'dataformat_' . $pluginname);
}, $dataformats);
}
/**
* Return list of available view as user options
*
* @return string[]
*/
public static function get_viewas_options(): array {
return [
model::REPORT_VIEWAS_CREATOR => get_string('scheduleviewascreator', 'core_reportbuilder'),
model::REPORT_VIEWAS_RECIPIENT => get_string('scheduleviewasrecipient', 'core_reportbuilder'),
model::REPORT_VIEWAS_USER => get_string('userselect', 'core_reportbuilder'),
];
}
/**
* Return list of recurrence options
*
* @return string[]
*/
public static function get_recurrence_options(): array {
return [
model::RECURRENCE_NONE => get_string('none'),
model::RECURRENCE_DAILY => get_string('recurrencedaily', 'core_reportbuilder'),
model::RECURRENCE_WEEKDAYS => get_string('recurrenceweekdays', 'core_reportbuilder'),
model::RECURRENCE_WEEKLY => get_string('recurrenceweekly', 'core_reportbuilder'),
model::RECURRENCE_MONTHLY => get_string('recurrencemonthly', 'core_reportbuilder'),
model::RECURRENCE_ANNUALLY => get_string('recurrenceannually', 'core_reportbuilder'),
];
}
/**
* Return list of options for when report is empty
*
* @return string[]
*/
public static function get_report_empty_options(): array {
return [
model::REPORT_EMPTY_SEND_EMPTY => get_string('scheduleemptysendwithattachment', 'core_reportbuilder'),
model::REPORT_EMPTY_SEND_WITHOUT => get_string('scheduleemptysendwithoutattachment', 'core_reportbuilder'),
model::REPORT_EMPTY_DONT_SEND => get_string('scheduleemptydontsend', 'core_reportbuilder'),
];
}
}
@@ -0,0 +1,175 @@
<?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_text;
/**
* This class handles the setting and retrieving of a users' filter values for given reports
*
* It is currently using the user preference API as a storage mechanism
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_filter_manager {
/** @var int The size of each chunk, matching the maximum length of a single user preference */
private const PREFERENCE_CHUNK_SIZE = 1333;
/** @var string The prefix used to name the stored user preferences */
private const PREFERENCE_NAME_PREFIX = 'reportbuilder-report-';
/**
* Generate user preference name for given report
*
* @param int $reportid
* @param int $index
* @return string
*/
private static function user_preference_name(int $reportid, int $index): string {
return static::PREFERENCE_NAME_PREFIX . "{$reportid}-{$index}";
}
/**
* Set user filters for given report
*
* @param int $reportid
* @param array $values
* @param int|null $userid
* @return bool
*/
public static function set(int $reportid, array $values, int $userid = null): bool {
$jsonvalues = json_encode($values);
$jsonchunks = str_split($jsonvalues, static::PREFERENCE_CHUNK_SIZE);
foreach ($jsonchunks as $index => $jsonchunk) {
$userpreference = static::user_preference_name($reportid, $index);
set_user_preference($userpreference, $jsonchunk, $userid);
}
// Ensure any subsequent preferences are reset (to account for number of chunks decreasing).
static::reset_all($reportid, $userid, $index + 1);
return true;
}
/**
* Get user filters for given report
*
* @param int $reportid
* @param int|null $userid
* @return array
*/
public static function get(int $reportid, int $userid = null): array {
$jsonvalues = '';
$index = 0;
// We'll repeatedly append chunks to our JSON string, until we hit one that is below the maximum length.
do {
$userpreference = static::user_preference_name($reportid, $index++);
$jsonchunk = get_user_preferences($userpreference, '', $userid);
$jsonvalues .= $jsonchunk;
} while (core_text::strlen($jsonchunk) === static::PREFERENCE_CHUNK_SIZE);
return (array) json_decode($jsonvalues);
}
/**
* Merge individual user filter values for given report
*
* @param int $reportid
* @param array $values
* @param int|null $userid
* @return bool
*/
public static function merge(int $reportid, array $values, int $userid = null): bool {
$existing = static::get($reportid, $userid);
return static::set($reportid, array_merge($existing, $values), $userid);
}
/**
* Reset all user filters for given report
*
* @param int $reportid
* @param int|null $userid
* @param int $index If specified, then preferences will be reset starting from this index
* @return bool
*/
public static function reset_all(int $reportid, int $userid = null, int $index = 0): bool {
// We'll repeatedly retrieve and reset preferences, until we hit one that is below the maximum length.
do {
$userpreference = static::user_preference_name($reportid, $index++);
$jsonchunk = get_user_preferences($userpreference, '', $userid);
unset_user_preference($userpreference, $userid);
} while (core_text::strlen($jsonchunk) === static::PREFERENCE_CHUNK_SIZE);
return true;
}
/**
* Reset single user filter for given report
*
* @param int $reportid
* @param string $uniqueidentifier
* @param int|null $userid
* @return bool
*/
public static function reset_single(int $reportid, string $uniqueidentifier, int $userid = null): bool {
$originalvalues = static::get($reportid, $userid);
// Remove any filters whose name is prefixed by given identifier.
$values = array_filter($originalvalues, static function(string $filterkey) use ($uniqueidentifier): bool {
return core_text::strpos($filterkey, $uniqueidentifier) !== 0;
}, ARRAY_FILTER_USE_KEY);
return static::set($reportid, $values, $userid);
}
/**
* Get all report filters for given user
*
* This is primarily designed for the privacy provider, and allows us to preserve all the preference logic within this class.
*
* @param int $userid
* @return array
*/
public static function get_all_for_user(int $userid): array {
global $DB;
$prefs = [];
// We need to locate the first preference chunk of all report filters.
$select = 'userid = :userid AND ' . $DB->sql_like('name', ':namelike');
$params = [
'userid' => $userid,
'namelike' => $DB->sql_like_escape(static::PREFERENCE_NAME_PREFIX) . '%-0',
];
$preferences = $DB->get_fieldset_select('user_preferences', 'name', $select, $params);
// Retrieve all found filters.
foreach ($preferences as $preference) {
preg_match('/^' . static::PREFERENCE_NAME_PREFIX . '(?<reportid>\d+)\-/', $preference, $matches);
$prefs[static::PREFERENCE_NAME_PREFIX . $matches['reportid']] = static::get((int) $matches['reportid'], $userid);
}
return $prefs;
}
}
@@ -0,0 +1,281 @@
<?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_text;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\date;
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 profile_field_base;
use stdClass;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/user/profile/lib.php');
/**
* Helper class for user profile fields.
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_profile_fields {
/** @var array user profile fields */
private $userprofilefields;
/** @var string $entityname Name of the entity */
private $entityname;
/** @var int $usertablefieldalias The user table/field alias */
private $usertablefieldalias;
/** @var array additional joins */
private $joins = [];
/**
* Class userprofilefields constructor.
*
* @param string $usertablefieldalias The user table/field alias used when adding columns and filters.
* @param string $entityname The entity name used when adding columns and filters.
*/
public function __construct(string $usertablefieldalias, string $entityname) {
$this->usertablefieldalias = $usertablefieldalias;
$this->entityname = $entityname;
$this->userprofilefields = $this->get_user_profile_fields();
}
/**
* Retrieves the list of available/visible user profile fields
*
* @return profile_field_base[]
*/
private function get_user_profile_fields(): array {
return array_filter(profile_get_user_fields_with_data(0), static function(profile_field_base $profilefield): bool {
return $profilefield->is_visible();
});
}
/**
* 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 profile 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 profile_field_base $profilefield
* @return string
*/
private function get_table_alias(profile_field_base $profilefield): string {
static $aliases = [];
$aliaskey = "{$this->entityname}_{$profilefield->fieldid}";
if (!array_key_exists($aliaskey, $aliases)) {
$aliases[$aliaskey] = database::generate_alias();
}
return $aliases[$aliaskey];
}
/**
* Get table join for given profile field
*
* @param profile_field_base $profilefield
* @return string
*/
private function get_table_join(profile_field_base $profilefield): string {
$userinfotablealias = $this->get_table_alias($profilefield);
return "LEFT JOIN {user_info_data} {$userinfotablealias}
ON {$userinfotablealias}.userid = {$this->usertablefieldalias}
AND {$userinfotablealias}.fieldid = {$profilefield->fieldid}";
}
/**
* Return the user profile fields visible columns.
*
* @return column[]
*/
public function get_columns(): array {
global $DB;
$columns = [];
foreach ($this->userprofilefields as $profilefield) {
$columntype = $this->get_user_field_type($profilefield->field->datatype);
$columnfieldsql = $this->get_table_alias($profilefield) . '.data';
// Numeric (checkbox/time) fields should be cast, as should all fields for Oracle, for aggregation support.
if ($columntype === column::TYPE_BOOLEAN || $columntype === column::TYPE_TIMESTAMP) {
$columnfieldsql = "CASE WHEN {$columnfieldsql} IS NULL THEN NULL ELSE " .
$DB->sql_cast_char2int($columnfieldsql, true) . " END";
} else if ($DB->get_dbfamily() === 'oracle') {
$columnfieldsql = $DB->sql_order_by_text($columnfieldsql, 1024);
}
$columns[] = (new column(
'profilefield_' . core_text::strtolower($profilefield->field->shortname),
new lang_string('customfieldcolumn', 'core_reportbuilder', $profilefield->display_name(false)),
$this->entityname
))
->add_joins($this->get_joins())
->add_join($this->get_table_join($profilefield))
->add_field($columnfieldsql, 'data')
->set_type($columntype)
->set_is_sortable($columntype !== column::TYPE_LONGTEXT)
->add_callback(static function($value, stdClass $row, profile_field_base $field): string {
if ($value === null) {
return '';
}
$field->data = $value;
return (string) $field->display_data();
}, $profilefield);
}
return $columns;
}
/**
* Get custom user profile fields filters.
*
* @return filter[]
*/
public function get_filters(): array {
global $DB;
$filters = [];
foreach ($this->userprofilefields as $profilefield) {
$field = $this->get_table_alias($profilefield) . '.data';
$params = [];
switch ($profilefield->field->datatype) {
case 'checkbox':
$classname = boolean_select::class;
$fieldsql = "COALESCE(" . $DB->sql_cast_char2int($field, true) . ", 0)";
break;
case 'datetime':
$classname = date::class;
$fieldsql = $DB->sql_cast_char2int($field, true);
break;
case 'menu':
$classname = select::class;
$emptyparam = database::generate_param_name();
$fieldsql = "COALESCE(" . $DB->sql_compare_text($field, 255) . ", :{$emptyparam})";
$params[$emptyparam] = '';
break;
case 'text':
case 'textarea':
default:
$classname = text::class;
$emptyparam = database::generate_param_name();
$fieldsql = "COALESCE(" . $DB->sql_compare_text($field, 255) . ", :{$emptyparam})";
$params[$emptyparam] = '';
break;
}
$filter = (new filter(
$classname,
'profilefield_' . core_text::strtolower($profilefield->field->shortname),
new lang_string('customfieldcolumn', 'core_reportbuilder', $profilefield->display_name(false)),
$this->entityname,
$fieldsql,
$params
))
->add_joins($this->get_joins())
->add_join($this->get_table_join($profilefield));
// If menu type then set filter options as appropriate.
if ($profilefield->field->datatype === 'menu') {
$filter->set_options($profilefield->options);
}
$filters[] = $filter;
}
return $filters;
}
/**
* Get user profile field type for report.
*
* @param string $userfield user field.
* @return int the constant equivalent to this custom field type.
*/
protected function get_user_field_type(string $userfield): int {
switch ($userfield) {
case 'checkbox':
$customfieldtype = column::TYPE_BOOLEAN;
break;
case 'datetime':
$customfieldtype = column::TYPE_TIMESTAMP;
break;
case 'textarea':
$customfieldtype = column::TYPE_LONGTEXT;
break;
case 'menu':
case 'text':
default:
$customfieldtype = column::TYPE_TEXT;
break;
}
return $customfieldtype;
}
}
@@ -0,0 +1,142 @@
<?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\models;
use context;
use core_reportbuilder\event\audience_created;
use core_reportbuilder\event\audience_deleted;
use core_reportbuilder\event\audience_updated;
use lang_string;
use core\persistent;
use core_reportbuilder\local\helpers\audience as helper;
/**
* Persistent class to represent a report audience
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class audience extends persistent {
/** @var string Table name */
public const TABLE = 'reportbuilder_audience';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties(): array {
return [
'reportid' => [
'type' => PARAM_INT,
],
'heading' => [
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED,
'default' => null,
],
'classname' => [
'type' => PARAM_TEXT,
],
'configdata' => [
'type' => PARAM_RAW,
'default' => '{}',
],
'usercreated' => [
'type' => PARAM_INT,
'default' => static function(): int {
global $USER;
return (int) $USER->id;
},
],
];
}
/**
* Validate reportid property
*
* @param int $reportid
* @return bool|lang_string
*/
protected function validate_reportid(int $reportid) {
if (!report::record_exists($reportid)) {
return new lang_string('invaliddata', 'error');
}
return true;
}
/**
* Hook to execute after creation
*/
protected function after_create(): void {
audience_created::create_from_object($this)->trigger();
helper::purge_caches();
}
/**
* Hook to execute after update
*
* @param bool $result
*/
protected function after_update($result): void {
if ($result) {
audience_updated::create_from_object($this)->trigger();
helper::purge_caches();
}
}
/**
* Hook to execute after deletion
*
* @param bool $result
*/
protected function after_delete($result): void {
if ($result) {
audience_deleted::create_from_object($this)->trigger();
helper::purge_caches();
}
}
/**
* Return the report this audience belongs to
*
* @return report
*/
public function get_report(): report {
return new report($this->get('reportid'));
}
/**
* Return formatted audience heading
*
* @param context|null $context If the context of the report is already known, it should be passed here
* @return string
*/
public function get_formatted_heading(?context $context = null): string {
if ($context === null) {
$context = $this->get_report()->get_context();
}
return format_string($this->raw_get('heading'), true, ['context' => $context]);
}
}
@@ -0,0 +1,167 @@
<?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\models;
use context;
use lang_string;
use core\persistent;
use core_reportbuilder\datasource;
/**
* Persistent class to represent a report column
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column extends persistent {
/** @var string The table name. */
public const TABLE = 'reportbuilder_column';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties(): array {
return [
'reportid' => [
'type' => PARAM_INT,
],
'uniqueidentifier' => [
'type' => PARAM_RAW,
],
'aggregation' => [
'type' => PARAM_ALPHANUMEXT,
'default' => null,
'null' => NULL_ALLOWED,
],
'heading' => [
'type' => PARAM_TEXT,
'default' => null,
'null' => NULL_ALLOWED,
],
'columnorder' => [
'type' => PARAM_INT,
],
'sortenabled' => [
'type' => PARAM_BOOL,
'default' => false,
],
'sortdirection' => [
'type' => PARAM_INT,
'choices' => [SORT_ASC, SORT_DESC],
'default' => SORT_ASC,
],
'sortorder' => [
'type' => PARAM_INT,
'default' => null,
'null' => NULL_ALLOWED,
],
'usercreated' => [
'type' => PARAM_INT,
'default' => static function(): int {
global $USER;
return (int) $USER->id;
},
],
];
}
/**
* Validate reportid property
*
* @param int $reportid
* @return bool|lang_string
*/
protected function validate_reportid(int $reportid) {
if (!report::record_exists($reportid)) {
return new lang_string('invaliddata', 'error');
}
return true;
}
/**
* Return the report this column belongs to
*
* @return report
*/
public function get_report(): report {
return new report($this->get('reportid'));
}
/**
* Ensure report source is notified of new column
*/
protected function after_create(): void {
datasource::report_elements_modified($this->get('reportid'));
}
/**
* Ensure report source is notified of updated column
*
* @param bool $result
*/
protected function after_update($result): void {
if ($result) {
datasource::report_elements_modified($this->get('reportid'));
}
}
/**
* Ensure report source is notified of deleted column
*
* @param bool $result
*/
protected function after_delete($result): void {
if ($result) {
datasource::report_elements_modified($this->get('reportid'));
}
}
/**
* Helper method to return the current maximum column order value for a report
*
* @param int $reportid
* @param string $columnname
* @return int
*/
public static function get_max_columnorder(int $reportid, string $columnname): int {
global $DB;
return (int) $DB->get_field(static::TABLE, "MAX({$columnname})", ['reportid' => $reportid], MUST_EXIST);
}
/**
* Return formatted column name
*
* @param context|null $context If the context of the report is already known, it should be passed here
* @return string
*/
public function get_formatted_heading(?context $context = null): string {
if ($context === null) {
$context = $this->get_report()->get_context();
}
return format_string($this->raw_get('heading'), true, ['context' => $context]);
}
}
@@ -0,0 +1,200 @@
<?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\models;
use context;
use lang_string;
use core\persistent;
use core_reportbuilder\datasource;
/**
* Persistent class to represent a report filter/condition
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filter extends persistent {
/** @var string The table name. */
public const TABLE = 'reportbuilder_filter';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties(): array {
return [
'reportid' => [
'type' => PARAM_INT,
],
'uniqueidentifier' => [
'type' => PARAM_RAW,
],
'heading' => [
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED,
'default' => null,
],
'iscondition' => [
'type' => PARAM_BOOL,
'default' => false,
],
'filterorder' => [
'type' => PARAM_INT,
],
'usercreated' => [
'type' => PARAM_INT,
'default' => static function(): int {
global $USER;
return (int) $USER->id;
},
],
];
}
/**
* Validate reportid property
*
* @param int $reportid
* @return bool|lang_string
*/
protected function validate_reportid(int $reportid) {
if (!report::record_exists($reportid)) {
return new lang_string('invaliddata', 'error');
}
return true;
}
/**
* Ensure report source is notified of new filter
*/
protected function after_create(): void {
datasource::report_elements_modified($this->get('reportid'));
}
/**
* Ensure report source is notified of updated filter
*
* @param bool $result
*/
protected function after_update($result): void {
if ($result) {
datasource::report_elements_modified($this->get('reportid'));
}
}
/**
* Ensure report source is notified of deleted filter
*
* @param bool $result
*/
protected function after_delete($result): void {
if ($result) {
datasource::report_elements_modified($this->get('reportid'));
}
}
/**
* Return the report this filter belongs to
*
* @return report
*/
public function get_report(): report {
return new report($this->get('reportid'));
}
/**
* Return filter record
*
* @param int $reportid
* @param int $filterid
* @return false|static
*/
public static function get_filter_record(int $reportid, int $filterid) {
return self::get_record(['id' => $filterid, 'reportid' => $reportid, 'iscondition' => 0]);
}
/**
* Return filter records for report
*
* @param int $reportid
* @param string $sort
* @param string $order
* @return static[]
*/
public static function get_filter_records(int $reportid, string $sort = '', string $order = 'ASC'): array {
return self::get_records(['reportid' => $reportid, 'iscondition' => 0], $sort, $order);
}
/**
* Return condition record
*
* @param int $reportid
* @param int $conditionid
* @return false|static
*/
public static function get_condition_record(int $reportid, int $conditionid) {
return self::get_record(['id' => $conditionid, 'reportid' => $reportid, 'iscondition' => 1]);
}
/**
* Return condition records for report
*
* @param int $reportid
* @param string $sort
* @param string $order
* @return static[]
*/
public static function get_condition_records(int $reportid, string $sort = '', string $order = 'ASC'): array {
return self::get_records(['reportid' => $reportid, 'iscondition' => 1], $sort, $order);
}
/**
* Helper method to return the current maximum filter order value for a report
*
* @param int $reportid
* @param bool $iscondition
* @return int
*/
public static function get_max_filterorder(int $reportid, bool $iscondition = false): int {
global $DB;
$params = ['reportid' => $reportid, 'iscondition' => (int) $iscondition];
return (int) $DB->get_field(static::TABLE, "MAX(filterorder)", $params, MUST_EXIST);
}
/**
* Return formatted filter heading
*
* @param context|null $context If the context of the report is already known, it should be passed here
* @return string
*/
public function get_formatted_heading(?context $context = null): string {
if ($context === null) {
$context = $this->get_report()->get_context();
}
return format_string($this->raw_get('heading'), true, ['context' => $context]);
}
}
@@ -0,0 +1,183 @@
<?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\models;
use context;
use context_system;
use core\persistent;
use core_reportbuilder\event\report_created;
use core_reportbuilder\event\report_deleted;
use core_reportbuilder\event\report_updated;
use core_reportbuilder\local\report\base;
/**
* Persistent class to represent a report
*
* @package core_reportbuilder
* @copyright 2018 Alberto Lara Hernández <albertolara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report extends persistent {
/** @var string The table name. */
public const TABLE = 'reportbuilder_report';
/**
* Return the definition of the properties of this model
*
* @return array
*/
protected static function define_properties(): array {
return [
'name' => [
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED,
'default' => null,
],
'source' => [
'type' => PARAM_RAW,
],
'type' => [
'type' => PARAM_INT,
'choices' => [
base::TYPE_CUSTOM_REPORT,
base::TYPE_SYSTEM_REPORT,
],
],
'uniquerows' => [
'type' => PARAM_BOOL,
'default' => false,
],
'conditiondata' => [
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'default' => null,
],
'settingsdata' => [
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'default' => null,
],
'contextid' => [
'type' => PARAM_INT,
'default' => static function(): int {
return context_system::instance()->id;
}
],
'component' => [
'type' => PARAM_COMPONENT,
'default' => '',
],
'area' => [
'type' => PARAM_AREA,
'default' => '',
],
'itemid' => [
'type' => PARAM_INT,
'default' => 0,
],
'usercreated' => [
'type' => PARAM_INT,
'default' => static function(): int {
global $USER;
return (int) $USER->id;
},
],
];
}
/**
* Trigger report created event when persistent is created
*/
protected function after_create(): void {
if ($this->get('type') === base::TYPE_CUSTOM_REPORT) {
report_created::create_from_object($this)->trigger();
}
}
/**
* Cascade report deletion, first deleting any linked persistents
*/
protected function before_delete(): void {
$reportparams = ['reportid' => $this->get('id')];
// Columns.
foreach (column::get_records($reportparams) as $column) {
$column->delete();
}
// Filters.
foreach (filter::get_records($reportparams) as $filter) {
$filter->delete();
}
// Audiences.
foreach (audience::get_records($reportparams) as $audience) {
$audience->delete();
}
// Schedules.
foreach (schedule::get_records($reportparams) as $schedule) {
$schedule->delete();
}
}
/**
* Throw report deleted event when persistent is deleted
*
* @param bool $result
*/
protected function after_delete($result): void {
if (!$result || $this->get('type') === base::TYPE_SYSTEM_REPORT) {
return;
}
report_deleted::create_from_object($this)->trigger();
}
/**
* Throw report updated event when persistent is updated
*
* @param bool $result
*/
protected function after_update($result): void {
if (!$result || $this->get('type') === base::TYPE_SYSTEM_REPORT) {
return;
}
report_updated::create_from_object($this)->trigger();
}
/**
* Return report context, used by exporters
*
* @return context
*/
public function get_context(): context {
return context::instance_by_id($this->raw_get('contextid'));
}
/**
* Return formatted report name
*
* @return string
*/
public function get_formatted_name(): string {
return format_string($this->raw_get('name'), true, ['context' => $this->get_context(), 'escape' => true]);
}
}
@@ -0,0 +1,227 @@
<?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\models;
use context;
use core_reportbuilder\event\schedule_created;
use core_reportbuilder\event\schedule_deleted;
use core_reportbuilder\event\schedule_updated;
use lang_string;
use core\persistent;
/**
* Persistent class to represent a report schedule
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class schedule extends persistent {
/** @var string Table name */
public const TABLE = 'reportbuilder_schedule';
/** @var int Send report schedule as viewed by recipient */
public const REPORT_VIEWAS_RECIPIENT = -1;
/** @var int Send report schedule as viewed by creator */
public const REPORT_VIEWAS_CREATOR = 0;
/** @var int Send report schedule as viewed by specific user */
public const REPORT_VIEWAS_USER = 1;
/** @var int No recurrence */
public const RECURRENCE_NONE = 0;
/** @var int Daily recurrence */
public const RECURRENCE_DAILY = 1;
/** @var int Daily recurrence for week days only */
public const RECURRENCE_WEEKDAYS = 2;
/** @var int Weekly recurrence */
public const RECURRENCE_WEEKLY = 3;
/** @var int Monthly recurrence */
public const RECURRENCE_MONTHLY = 4;
/** @var int Annual recurrence */
public const RECURRENCE_ANNUALLY = 5;
/** @var int Send schedule with empty report */
public const REPORT_EMPTY_SEND_EMPTY = 0;
/** @var int Send schedule without report */
public const REPORT_EMPTY_SEND_WITHOUT = 1;
/** @var int Don't send schedule if report is empty */
public const REPORT_EMPTY_DONT_SEND = 2;
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties(): array {
return [
'reportid' => [
'type' => PARAM_INT,
],
'name' => [
'type' => PARAM_TEXT,
],
'enabled' => [
'type' => PARAM_BOOL,
'default' => true,
],
'audiences' => [
'type' => PARAM_RAW,
'default' => '[]',
],
'format' => [
'type' => PARAM_PLUGIN,
],
'subject' => [
'type' => PARAM_TEXT,
],
'message' => [
'type' => PARAM_CLEANHTML,
],
'messageformat' => [
'type' => PARAM_INT,
'default' => FORMAT_HTML,
'choices' => [
FORMAT_MOODLE,
FORMAT_HTML,
FORMAT_PLAIN,
FORMAT_MARKDOWN,
],
],
'userviewas' => [
'type' => PARAM_INT,
'default' => self::REPORT_VIEWAS_CREATOR,
],
'timescheduled' => [
'type' => PARAM_INT,
],
'recurrence' => [
'type' => PARAM_INT,
'default' => self::RECURRENCE_NONE,
'choices' => [
self::RECURRENCE_NONE,
self::RECURRENCE_DAILY,
self::RECURRENCE_WEEKDAYS,
self::RECURRENCE_WEEKLY,
self::RECURRENCE_MONTHLY,
self::RECURRENCE_ANNUALLY,
],
],
'reportempty' => [
'type' => PARAM_INT,
'default' => self::REPORT_EMPTY_SEND_EMPTY,
'choices' => [
self::REPORT_EMPTY_SEND_EMPTY,
self::REPORT_EMPTY_SEND_WITHOUT,
self::REPORT_EMPTY_DONT_SEND,
],
],
'timelastsent' => [
'type' => PARAM_INT,
'default' => 0,
],
'timenextsend' => [
'type' => PARAM_INT,
'default' => 0,
],
'usercreated' => [
'type' => PARAM_INT,
'default' => static function(): int {
global $USER;
return (int) $USER->id;
},
],
];
}
/**
* Validate reportid property
*
* @param int $reportid
* @return bool|lang_string
*/
protected function validate_reportid(int $reportid) {
if (!report::record_exists($reportid)) {
return new lang_string('invaliddata', 'error');
}
return true;
}
/**
* Return the report this schedule belongs to
*
* @return report
*/
public function get_report(): report {
return new report($this->get('reportid'));
}
/**
* Return formatted schedule name
*
* @param context|null $context If the context of the report is already known, it should be passed here
* @return string
*/
public function get_formatted_name(?context $context = null): string {
if ($context === null) {
$context = $this->get_report()->get_context();
}
return format_string($this->raw_get('name'), true, ['context' => $context]);
}
/**
* Hook to execute after creation
*/
protected function after_create(): void {
schedule_created::create_from_object($this)->trigger();
}
/**
* Hook to execute after update
*
* @param bool $result
*/
protected function after_update($result): void {
if ($result) {
schedule_updated::create_from_object($this)->trigger();
}
}
/**
* Hook to execute after deletion
*
* @param bool $result
*/
protected function after_delete($result): void {
if ($result) {
schedule_deleted::create_from_object($this)->trigger();
}
}
}
@@ -0,0 +1,154 @@
<?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\report;
use action_menu_link;
use lang_string;
use moodle_url;
use pix_icon;
use popup_action;
use stdClass;
/**
* Class to represent a report action
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class action {
/** @var moodle_url $url */
protected $url;
/** @var pix_icon $icon */
protected $icon;
/** @var array $attributes */
protected $attributes;
/** @var bool $popup */
protected $popup;
/** @var callable[] $callbacks */
protected $callbacks = [];
/** @var lang_string|string $title */
protected $title;
/**
* Create an instance of an action to be added to a report. Both the parameters of the URL, and the attributes parameter
* support placeholders which will be replaced with appropriate row values, e.g.:
*
* new action(new moodle_url('/', ['id' => ':id']), new pix_icon(...), ['data-id' => ':id'])
*
* Note that all expected placeholders should be added as base fields to the report
*
* @param moodle_url $url
* @param pix_icon $icon
* @param string[] $attributes Array of attributes to include in action, each will be cast to string prior to use
* @param bool $popup
* @param ?lang_string $title
*/
public function __construct(
moodle_url $url,
pix_icon $icon,
array $attributes = [],
bool $popup = false,
?lang_string $title = null
) {
$this->url = $url;
$this->icon = $icon;
$this->attributes = $attributes;
$this->popup = $popup;
// If title is not passed, check the title attribute from the icon.
$this->title = $title ?? $icon->attributes['title'] ?? '';
}
/**
* Adds callback to the action. Used to verify action is available to current user, or preprocess values used in placeholders
*
* Multiple callbacks can be added. If at least one returns false then the action will not be displayed
*
* @param callable $callback
* @return self
*/
public function add_callback(callable $callback): self {
$this->callbacks[] = $callback;
return $this;
}
/**
* Return action menu link suitable for output, or null if the action cannot be displayed (because one of its callbacks
* returned false, {@see add_callback})
*
* @param stdClass $row
* @return action_menu_link|null
*/
public function get_action_link(stdClass $row): ?action_menu_link {
foreach ($this->callbacks as $callback) {
$row = clone $row; // Clone so we don't modify the shared row inside a callback.
if (!$callback($row)) {
return null;
}
}
// Create a new moodle_url instance with our filled in placeholders for this row.
$url = new moodle_url(
$this->url->out_omit_querystring(true),
self::replace_placeholders($this->url->params(), $row)
);
// Ensure we have a title attribute set, if one wasn't already provided.
if (!array_key_exists('title', $this->attributes)) {
$this->attributes['title'] = (string) $this->title;
}
$this->attributes['aria-label'] = $this->attributes['title'];
if ($this->popup) {
$this->attributes['data-action'] = 'report-action-popup';
$this->attributes['data-popup-action'] = json_encode(new popup_action('click', $url));
}
// Interpolate any placeholders with correct values.
$attributes = self::replace_placeholders($this->attributes, $row);
// Ensure title attribute isn't duplicated.
$title = $attributes['title'];
unset($attributes['title']);
return new action_menu_link($url, $this->icon, $title, null, $attributes);
}
/**
* Given an array of values, replace all placeholders with corresponding property of the given row
*
* @param string[] $values
* @param stdClass $row
* @return array
*/
private static function replace_placeholders(array $values, stdClass $row): array {
return array_map(static function($value) use ($row) {
return preg_replace_callback('/^:(?<property>.*)$/', static function(array $matches) use ($row): string {
return (string) ($row->{$matches['property']} ?? '');
}, (string) $value);
}, $values);
}
}
+833
View File
@@ -0,0 +1,833 @@
<?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\report;
use coding_exception;
use context;
use lang_string;
use core_reportbuilder\local\entities\base as entity_base;
use core_reportbuilder\local\filters\base as filter_base;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\helpers\user_filter_manager;
use core_reportbuilder\local\models\report;
/**
* Base class for all reports
*
* @package core_reportbuilder
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base {
/** @var int Custom report type value */
public const TYPE_CUSTOM_REPORT = 0;
/** @var int System report type value */
public const TYPE_SYSTEM_REPORT = 1;
/** @var int Default paging limit */
public const DEFAULT_PAGESIZE = 30;
/** @var report $report Report persistent */
private $report;
/** @var string $maintable */
private $maintable = '';
/** @var string $maintablealias */
private $maintablealias = '';
/** @var array $sqljoins */
private $sqljoins = [];
/** @var array $sqlwheres */
private $sqlwheres = [];
/** @var array $sqlparams */
private $sqlparams = [];
/** @var entity_base[] $entities */
private $entities = [];
/** @var lang_string[] */
private $entitytitles = [];
/** @var column[] $columns */
private $columns = [];
/** @var filter[] $conditions */
private $conditions = [];
/** @var filter[] $filters */
private $filters = [];
/** @var bool $downloadable Set if the report can be downloaded */
private $downloadable = false;
/** @var string $downloadfilename Name of the downloaded file */
private $downloadfilename = '';
/** @var int Default paging size */
private $defaultperpage = self::DEFAULT_PAGESIZE;
/** @var array $attributes */
private $attributes = [];
/** @var lang_string $noresultsnotice */
private $noresultsnotice;
/**
* Base report constructor
*
* @param report $report
*/
public function __construct(report $report) {
$this->report = $report;
$this->noresultsnotice = new lang_string('nothingtodisplay');
// Initialise and validate the report.
$this->initialise();
$this->validate();
}
/**
* Returns persistent class used when initialising this report
*
* @return report
*/
final public function get_report_persistent(): report {
return $this->report;
}
/**
* Return user friendly name of the report
*
* @return string
*/
abstract public static function get_name(): string;
/**
* Initialise report. Specify which columns, filters, etc should be present
*
* To set the base query use:
* - {@see set_main_table}
* - {@see add_base_condition_simple} or {@see add_base_condition_sql}
* - {@see add_join}
*
* To add content to the report use:
* - {@see add_entity}
* - {@see add_column}
* - {@see add_filter}
* - etc
*/
abstract protected function initialise(): void;
/**
* Get the report availability. Sub-classes should override this method to declare themselves unavailable, for example if
* they require classes that aren't present due to missing plugin
*
* @return bool
*/
public static function is_available(): bool {
return true;
}
/**
* Perform some basic validation about expected class properties
*
* @throws coding_exception
*/
protected function validate(): void {
if (empty($this->maintable)) {
throw new coding_exception('Report must define main table by calling $this->set_main_table()');
}
if (empty($this->columns)) {
throw new coding_exception('Report must define at least one column by calling $this->add_column()');
}
}
/**
* Set the main table and alias for the SQL query
*
* @param string $tablename
* @param string $tablealias
*/
final public function set_main_table(string $tablename, string $tablealias = ''): void {
$this->maintable = $tablename;
$this->maintablealias = $tablealias;
}
/**
* Get the main table name
*
* @return string
*/
final public function get_main_table(): string {
return $this->maintable;
}
/**
* Get the alias for the main table
*
* @return string
*/
final public function get_main_table_alias(): string {
return $this->maintablealias;
}
/**
* Adds report JOIN clause that is always added
*
* @param string $join
* @param array $params
* @param bool $validateparams Some queries might add non-standard params and validation could fail
*/
protected function add_join(string $join, array $params = [], bool $validateparams = true): void {
if ($validateparams) {
database::validate_params($params);
}
$this->sqljoins[trim($join)] = trim($join);
$this->sqlparams += $params;
}
/**
* Return report JOIN clauses
*
* @return array
*/
public function get_joins(): array {
return array_values($this->sqljoins);
}
/**
* Define simple "field = value" clause to apply to the report query
*
* @param string $fieldname
* @param mixed $fieldvalue
*/
final public function add_base_condition_simple(string $fieldname, $fieldvalue): void {
if ($fieldvalue === null) {
$this->add_base_condition_sql("{$fieldname} IS NULL");
} else {
$fieldvalueparam = database::generate_param_name();
$this->add_base_condition_sql("{$fieldname} = :{$fieldvalueparam}", [
$fieldvalueparam => $fieldvalue,
]);
}
}
/**
* Define more complex/non-empty clause to apply to the report query
*
* @param string $where
* @param array $params Note that the param names should be generated by {@see database::generate_param_name}
*/
final public function add_base_condition_sql(string $where, array $params = []): void {
// Validate parameters always, so that potential errors are caught early.
database::validate_params($params);
if ($where !== '') {
$this->sqlwheres[] = trim($where);
$this->sqlparams = $params + $this->sqlparams;
}
}
/**
* Return base select/params for the report query
*
* @return array [string $select, array $params]
*/
final public function get_base_condition(): array {
return [
implode(' AND ', $this->sqlwheres),
$this->sqlparams,
];
}
/**
* Adds given entity, along with it's columns and filters, to the report
*
* @param entity_base $entity
*/
final protected function add_entity(entity_base $entity): void {
$entityname = $entity->get_entity_name();
$this->annotate_entity($entityname, $entity->get_entity_title());
$this->entities[$entityname] = $entity->initialise();
}
/**
* Returns the entity added to the report from the given entity name
*
* @param string $name
* @return entity_base
* @throws coding_exception
*/
final protected function get_entity(string $name): entity_base {
if (!array_key_exists($name, $this->entities)) {
throw new coding_exception('Invalid entity name', $name);
}
return $this->entities[$name];
}
/**
* Returns the list of all the entities added to the report
*
* @return entity_base[]
*/
final protected function get_entities(): array {
return $this->entities;
}
/**
* Define a new entity for the report
*
* @param string $name
* @param lang_string $title
* @throws coding_exception
*/
final protected function annotate_entity(string $name, lang_string $title): void {
if ($name === '' || $name !== clean_param($name, PARAM_ALPHANUMEXT)) {
throw new coding_exception('Entity name must be comprised of alphanumeric character, underscore or dash');
}
if (array_key_exists($name, $this->entitytitles)) {
throw new coding_exception('Duplicate entity name', $name);
}
$this->entitytitles[$name] = $title;
}
/**
* Returns title of given report entity
*
* @param string $name
* @return lang_string
* @throws coding_exception
*/
final public function get_entity_title(string $name): lang_string {
if (!array_key_exists($name, $this->entitytitles)) {
throw new coding_exception('Invalid entity name', $name);
}
return $this->entitytitles[$name];
}
/**
* Adds a column to the report
*
* @param column $column
* @return column
* @throws coding_exception
*/
final protected function add_column(column $column): column {
if (!array_key_exists($column->get_entity_name(), $this->entitytitles)) {
throw new coding_exception('Invalid entity name', $column->get_entity_name());
}
$name = $column->get_name();
if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) {
throw new coding_exception('Column name must be comprised of alphanumeric character, underscore or dash');
}
$uniqueidentifier = $column->get_unique_identifier();
if (array_key_exists($uniqueidentifier, $this->columns)) {
throw new coding_exception('Duplicate column identifier', $uniqueidentifier);
}
$this->columns[$uniqueidentifier] = $column;
return $column;
}
/**
* Add given column to the report from an entity
*
* The entity must have already been added to the report before calling this method
*
* @param string $uniqueidentifier
* @return column
*/
final protected function add_column_from_entity(string $uniqueidentifier): column {
[$entityname, $columnname] = explode(':', $uniqueidentifier, 2);
return $this->add_column($this->get_entity($entityname)->get_column($columnname));
}
/**
* Add given columns to the report from one or more entities
*
* Each entity must have already been added to the report before calling this method
*
* @param string[] $columns Unique identifier of each entity column
*/
final protected function add_columns_from_entities(array $columns): void {
foreach ($columns as $column) {
$this->add_column_from_entity($column);
}
}
/**
* Return report column by unique identifier
*
* @param string $uniqueidentifier
* @return column|null
*/
final public function get_column(string $uniqueidentifier): ?column {
return $this->columns[$uniqueidentifier] ?? null;
}
/**
* Return all available report columns
*
* @return column[]
*/
final public function get_columns(): array {
return array_filter($this->columns, static function(column $column): bool {
return $column->get_is_available();
});
}
/**
* Return all active report columns (by default, all available columns)
*
* @return column[]
*/
public function get_active_columns(): array {
$columns = $this->get_columns();
foreach ($columns as $column) {
if ($column->get_is_deprecated()) {
debugging("The column '{$column->get_unique_identifier()}' is deprecated, please do not use it any more." .
" {$column->get_is_deprecated_message()}", DEBUG_DEVELOPER);
}
}
return $columns;
}
/**
* Return all active report columns, keyed by their alias (only active columns in a report would have a valid alias/index)
*
* @return column[]
*/
final public function get_active_columns_by_alias(): array {
$columns = [];
foreach ($this->get_active_columns() as $column) {
$columns[$column->get_column_alias()] = $column;
}
return $columns;
}
/**
* Adds a condition to the report
*
* @param filter $condition
* @return filter
* @throws coding_exception
*/
final protected function add_condition(filter $condition): filter {
if (!array_key_exists($condition->get_entity_name(), $this->entitytitles)) {
throw new coding_exception('Invalid entity name', $condition->get_entity_name());
}
$name = $condition->get_name();
if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) {
throw new coding_exception('Condition name must be comprised of alphanumeric character, underscore or dash');
}
$uniqueidentifier = $condition->get_unique_identifier();
if (array_key_exists($uniqueidentifier, $this->conditions)) {
throw new coding_exception('Duplicate condition identifier', $uniqueidentifier);
}
$this->conditions[$uniqueidentifier] = $condition;
return $condition;
}
/**
* Add given condition to the report from an entity
*
* The entity must have already been added to the report before calling this method
*
* @param string $uniqueidentifier
* @return filter
*/
final protected function add_condition_from_entity(string $uniqueidentifier): filter {
[$entityname, $conditionname] = explode(':', $uniqueidentifier, 2);
return $this->add_condition($this->get_entity($entityname)->get_condition($conditionname));
}
/**
* Add given conditions to the report from one or more entities
*
* Each entity must have already been added to the report before calling this method
*
* @param string[] $conditions Unique identifier of each entity condition
*/
final protected function add_conditions_from_entities(array $conditions): void {
foreach ($conditions as $condition) {
$this->add_condition_from_entity($condition);
}
}
/**
* Return report condition by unique identifier
*
* @param string $uniqueidentifier
* @return filter|null
*/
final public function get_condition(string $uniqueidentifier): ?filter {
return $this->conditions[$uniqueidentifier] ?? null;
}
/**
* Return all available report conditions
*
* @return filter[]
*/
final public function get_conditions(): array {
return array_filter($this->conditions, static function(filter $condition): bool {
return $condition->get_is_available();
});
}
/**
* Return all active report conditions (by default, all available conditions)
*
* @return filter[]
*/
public function get_active_conditions(): array {
$conditions = $this->get_conditions();
foreach ($conditions as $condition) {
if ($condition->get_is_deprecated()) {
debugging("The condition '{$condition->get_unique_identifier()}' is deprecated, please do not use it any more." .
" {$condition->get_is_deprecated_message()}", DEBUG_DEVELOPER);
}
}
return $conditions;
}
/**
* Return all active report condition instances
*
* @return filter_base[]
*/
final public function get_condition_instances(): array {
return array_map(static function(filter $condition): filter_base {
/** @var filter_base $conditionclass */
$conditionclass = $condition->get_filter_class();
return $conditionclass::create($condition);
}, $this->get_active_conditions());
}
/**
* Set the condition values of the report
*
* @param array $values
* @return bool
*/
final public function set_condition_values(array $values): bool {
$this->report->set('conditiondata', json_encode($values))
->save();
return true;
}
/**
* Get the condition values of the report
*
* @return array
*/
final public function get_condition_values(): array {
$conditions = (string) $this->report->get('conditiondata');
return (array) json_decode($conditions);
}
/**
* Set the settings values of the report
*
* @param array $values
* @return bool
*/
final public function set_settings_values(array $values): bool {
$currentsettings = $this->get_settings_values();
$settings = array_merge($currentsettings, $values);
$this->report->set('settingsdata', json_encode($settings))
->save();
return true;
}
/**
* Get the settings values of the report
*
* @return array
*/
final public function get_settings_values(): array {
$settings = (string) $this->report->get('settingsdata');
return (array) json_decode($settings);
}
/**
* Adds a filter to the report
*
* @param filter $filter
* @return filter
* @throws coding_exception
*/
final protected function add_filter(filter $filter): filter {
if (!array_key_exists($filter->get_entity_name(), $this->entitytitles)) {
throw new coding_exception('Invalid entity name', $filter->get_entity_name());
}
$name = $filter->get_name();
if (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) {
throw new coding_exception('Filter name must be comprised of alphanumeric character, underscore or dash');
}
$uniqueidentifier = $filter->get_unique_identifier();
if (array_key_exists($uniqueidentifier, $this->filters)) {
throw new coding_exception('Duplicate filter identifier', $uniqueidentifier);
}
$this->filters[$uniqueidentifier] = $filter;
return $filter;
}
/**
* Add given filter to the report from an entity
*
* The entity must have already been added to the report before calling this method
*
* @param string $uniqueidentifier
* @return filter
*/
final protected function add_filter_from_entity(string $uniqueidentifier): filter {
[$entityname, $filtername] = explode(':', $uniqueidentifier, 2);
return $this->add_filter($this->get_entity($entityname)->get_filter($filtername));
}
/**
* Add given filters to the report from one or more entities
*
* Each entity must have already been added to the report before calling this method
*
* @param string[] $filters Unique identifier of each entity filter
*/
final protected function add_filters_from_entities(array $filters): void {
foreach ($filters as $filter) {
$this->add_filter_from_entity($filter);
}
}
/**
* Return report filter by unique identifier
*
* @param string $uniqueidentifier
* @return filter|null
*/
final public function get_filter(string $uniqueidentifier): ?filter {
return $this->filters[$uniqueidentifier] ?? null;
}
/**
* Return all available report filters
*
* @return filter[]
*/
final public function get_filters(): array {
return array_filter($this->filters, static function(filter $filter): bool {
return $filter->get_is_available();
});
}
/**
* Return all active report filters (by default, all available filters)
*
* @return filter[]
*/
public function get_active_filters(): array {
$filters = $this->get_filters();
foreach ($filters as $filter) {
if ($filter->get_is_deprecated()) {
debugging("The filter '{$filter->get_unique_identifier()}' is deprecated, please do not use it any more." .
" {$filter->get_is_deprecated_message()}", DEBUG_DEVELOPER);
}
}
return $filters;
}
/**
* Return all active report filter instances
*
* @return filter_base[]
*/
final public function get_filter_instances(): array {
return array_map(static function(filter $filter): filter_base {
/** @var filter_base $filterclass */
$filterclass = $filter->get_filter_class();
return $filterclass::create($filter);
}, $this->get_active_filters());
}
/**
* Set the filter values of the report
*
* @param array $values
* @return bool
*/
final public function set_filter_values(array $values): bool {
return user_filter_manager::set($this->report->get('id'), $values);
}
/**
* Get the filter values of the report
*
* @return array
*/
final public function get_filter_values(): array {
return user_filter_manager::get($this->report->get('id'));
}
/**
* Return the number of filter instances that are being applied based on the report's filter values (i.e. user has
* configured them from their initial "Any value" state)
*
* @return int
*/
final public function get_applied_filter_count(): int {
$values = $this->get_filter_values();
$applied = array_filter($this->get_filter_instances(), static function(filter_base $filter) use ($values): bool {
return $filter->applies_to_values($values);
});
return count($applied);
}
/**
* Set if the report can be downloaded.
*
* @param bool $downloadable
* @param string|null $downloadfilename If downloadable, then the name of the file (defaults to the name of the current report)
*/
final public function set_downloadable(bool $downloadable, ?string $downloadfilename = null): void {
$this->downloadable = $downloadable;
$this->downloadfilename = $downloadfilename ?? static::get_name();
}
/**
* Get if the report can be downloaded.
*
* @return bool
*/
final public function is_downloadable(): bool {
return $this->downloadable;
}
/**
* Return the downloadable report filename
*
* @return string
*/
final public function get_downloadfilename(): string {
return $this->downloadfilename;
}
/**
* Returns the report context
*
* @return context
*/
public function get_context(): context {
return $this->report->get_context();
}
/**
* Set the default 'per page' size
*
* @param int $defaultperpage
*/
public function set_default_per_page(int $defaultperpage): void {
$this->defaultperpage = $defaultperpage;
}
/**
* Set the default lang string for the notice used when no results are found.
*
* Note this should be called from within the report class instance itself (ideally it would be protected)
*
* @param lang_string|null $notice string, or null to tell the report to omit the notice entirely.
*/
public function set_default_no_results_notice(?lang_string $notice): void {
$this->noresultsnotice = $notice;
}
/**
* Get the default lang string for the notice used when no results are found.
*
* @return lang_string|null the lang_string instance or null if the report prefers not to use one.
*/
public function get_default_no_results_notice(): ?lang_string {
return $this->noresultsnotice;
}
/**
* Default 'per page' size
*
* @return int
*/
public function get_default_per_page(): int {
return $this->defaultperpage;
}
/**
* Add report attributes (data-, class, etc.) that will be included in HTML when report is displayed
*
* @param array $attributes
* @return self
*/
public function add_attributes(array $attributes): self {
$this->attributes = $attributes + $this->attributes;
return $this;
}
/**
* Returns the report HTML attributes
*
* @return array
*/
public function get_attributes(): array {
return $this->attributes;
}
}
@@ -0,0 +1,796 @@
<?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\report;
use coding_exception;
use lang_string;
use core_reportbuilder\local\helpers\aggregation;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\aggregation\base;
use core_reportbuilder\local\models\column as column_model;
/**
* Class to represent a report column
*
* @package core_reportbuilder
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class column {
/** @var int Column type is integer */
public const TYPE_INTEGER = 1;
/** @var int Column type is text */
public const TYPE_TEXT = 2;
/** @var int Column type is timestamp */
public const TYPE_TIMESTAMP = 3;
/** @var int Column type is boolean */
public const TYPE_BOOLEAN = 4;
/** @var int Column type is float */
public const TYPE_FLOAT = 5;
/** @var int Column type is long text */
public const TYPE_LONGTEXT = 6;
/** @var int $index Column index within a report */
private $index;
/** @var string $columnname Internal reference to name of column */
private $columnname;
/** @var lang_string $columntitle Used as a title for the column in reports */
private $columntitle;
/** @var bool $hascustomcolumntitle Used to store if the column has been given a custom title */
private $hascustomcolumntitle = false;
/** @var string $entityname Name of the entity this column belongs to */
private $entityname;
/** @var int $type Column data type (one of the TYPE_* class constants) */
private $type = self::TYPE_TEXT;
/** @var string[] $joins List of SQL joins for this column */
private $joins = [];
/** @var array $fields */
private $fields = [];
/** @var array $params */
private $params = [];
/** @var string $groupbysql */
private $groupbysql;
/** @var array[] $callbacks Array of [callable, additionalarguments] */
private $callbacks = [];
/** @var base|null $aggregation Aggregation type to apply to column */
private $aggregation = null;
/** @var array $disabledaggregation Aggregation types explicitly disabled */
private $disabledaggregation = [];
/** @var bool $issortable Used to indicate if a column is sortable */
private $issortable = false;
/** @var array $sortfields Fields to sort the column by */
private $sortfields = [];
/** @var array $attributes */
private $attributes = [];
/** @var bool $available Used to know if column is available to the current user or not */
protected $available = true;
/** @var bool $deprecated */
protected $deprecated = false;
/** @var string $deprecatedmessage */
protected $deprecatedmessage;
/** @var column_model $persistent */
protected $persistent;
/**
* Column constructor
*
* For better readability use chainable methods, for example:
*
* $report->add_column(
* (new column('name', new lang_string('name'), 'user'))
* ->add_join('left join {table} t on t.id = p.tableid')
* ->add_field('t.name')
* ->add_callback([format::class, 'format_string']));
*
* @param string $name Internal name of the column
* @param lang_string|null $title Title of the column used in reports (null for blank)
* @param string $entityname Name of the entity this column belongs to. Typically when creating columns within entities
* this value should be the result of calling {@see get_entity_name}, however if creating columns inside reports directly
* it should be the name of the entity as passed to {@see \core_reportbuilder\local\report\base::annotate_entity}
*/
public function __construct(string $name, ?lang_string $title, string $entityname) {
$this->columnname = $name;
$this->columntitle = $title;
$this->entityname = $entityname;
}
/**
* Set column name
*
* @param string $name
* @return self
*/
public function set_name(string $name): self {
$this->columnname = $name;
return $this;
}
/**
* Return column name
*
* @return mixed
*/
public function get_name(): string {
return $this->columnname;
}
/**
* Set column title
*
* @param lang_string|null $title
* @return self
*/
public function set_title(?lang_string $title): self {
$this->columntitle = $title;
$this->hascustomcolumntitle = true;
return $this;
}
/**
* Return column title
*
* @return string
*/
public function get_title(): string {
return $this->columntitle ? (string) $this->columntitle : '';
}
/**
* Check whether this column has been given a custom title
*
* @return bool
*/
public function has_custom_title(): bool {
return $this->hascustomcolumntitle;
}
/**
* Get column entity name
*
* @return string
*/
public function get_entity_name(): string {
return $this->entityname;
}
/**
* Return unique identifier for this column
*
* @return string
*/
public function get_unique_identifier(): string {
return $this->get_entity_name() . ':' . $this->get_name();
}
/**
* Set the column index within the current report
*
* @param int $index
* @return self
*/
public function set_index(int $index): self {
$this->index = $index;
return $this;
}
/**
* Set the column type, if not called then the type will be assumed to be {@see TYPE_TEXT}
*
* The type of a column is used to cast the first column field passed to any callbacks {@see add_callback} as well as the
* aggregation options available for the column
*
* @param int $type
* @return self
* @throws coding_exception
*/
public function set_type(int $type): self {
$allowedtypes = [
self::TYPE_INTEGER,
self::TYPE_TEXT,
self::TYPE_TIMESTAMP,
self::TYPE_BOOLEAN,
self::TYPE_FLOAT,
self::TYPE_LONGTEXT,
];
if (!in_array($type, $allowedtypes)) {
throw new coding_exception('Invalid column type', $type);
}
$this->type = $type;
return $this;
}
/**
* Return column type, that being one of the TYPE_* class constants
*
* @return int
*/
public function get_type(): int {
return $this->type;
}
/**
* Add join clause required for this column to join to existing tables/entities
*
* This is necessary in the case where {@see add_field} is selecting data from a table that isn't otherwise queried
*
* @param string $join
* @return self
*/
public function add_join(string $join): self {
$this->joins[trim($join)] = trim($join);
return $this;
}
/**
* Add multiple join clauses required for this column, passing each to {@see add_join}
*
* Typically when defining columns in entities, you should pass {@see \core_reportbuilder\local\report\base::get_joins} to
* this method, so that all entity joins are included in the report when your column is added to it
*
* @param string[] $joins
* @return self
*/
public function add_joins(array $joins): self {
foreach ($joins as $join) {
$this->add_join($join);
}
return $this;
}
/**
* Return column joins
*
* @return string[]
*/
public function get_joins(): array {
return array_values($this->joins);
}
/**
* Adds a field to be queried from the database that is necessary for this column
*
* Multiple fields can be added per column, this method may be called several times. Field aliases must be unique inside
* any given column, but there will be no conflicts if the same aliases are used in other columns in the same report
*
* @param string $sql SQL query, this may be a simple "tablealias.fieldname" or a complex sub-query that returns only one field
* @param string $alias
* @param array $params
* @return self
* @throws coding_exception
*/
public function add_field(string $sql, string $alias = '', array $params = []): self {
database::validate_params($params);
// SQL ends with a space and a word - this looks like an alias was passed as part of the field.
if (preg_match('/ \w+$/', $sql) && empty($alias)) {
throw new coding_exception('Column alias must be passed as a separate argument', $sql);
}
// If no alias was specified, auto-detect it based on common patterns ("table.column" or just "column").
if (empty($alias) && preg_match('/^(\w+\.)?(?<fieldname>\w+)$/', $sql, $matches)) {
$alias = $matches['fieldname'];
}
if (empty($alias)) {
throw new coding_exception('Complex columns must have an alias', $sql);
}
$this->fields[$alias] = $sql;
$this->params += $params;
return $this;
}
/**
* Add a list of comma-separated fields
*
* @param string $sql
* @param array $params
* @return self
*/
public function add_fields(string $sql, array $params = []): self {
database::validate_params($params);
// Split SQL into separate fields (separated by comma).
$fields = preg_split('/\s*,\s*/', $sql);
foreach ($fields as $field) {
// Split each field into expression, <field> <as> <alias> where "as" and "alias" are optional.
$fieldparts = preg_split('/\s+/', $field);
if (count($fieldparts) == 2 || (count($fieldparts) == 3 && strtolower($fieldparts[1]) === 'as')) {
$sql = reset($fieldparts);
$alias = array_pop($fieldparts);
$this->add_field($sql, $alias);
} else {
$this->add_field($field);
}
}
$this->params += $params;
return $this;
}
/**
* Given a param name, add a unique prefix to ensure that the same column with params can be added multiple times to a report
*
* @param string $name
* @return string
*/
private function unique_param_name(string $name): string {
return "p{$this->index}_{$name}";
}
/**
* Helper method to take all fields added to the column, and return appropriate SQL and alias
*
* @return array[]
*/
private function get_fields_sql_alias(): array {
$fields = [];
foreach ($this->fields as $alias => $sql) {
// Ensure parameter names within SQL are prefixed with column index.
$params = array_keys($this->params);
$sql = database::sql_replace_parameter_names($sql, $params, function(string $param): string {
return $this->unique_param_name($param);
});
$fields[$alias] = [
'sql' => $sql,
'alias' => substr("c{$this->index}_{$alias}", 0, 30),
];
}
return $fields;
}
/**
* Return array of SQL expressions for each field of this column
*
* @return array
*/
public function get_fields(): array {
$fieldsalias = $this->get_fields_sql_alias();
if (!empty($this->aggregation)) {
$fieldsaliassql = array_column($fieldsalias, 'sql');
$field = reset($fieldsalias);
// If aggregating the column, generate SQL from column fields and use it to generate aggregation SQL.
$columnfieldsql = $this->aggregation::get_column_field_sql($fieldsaliassql);
$aggregationfieldsql = $this->aggregation::get_field_sql($columnfieldsql, $this->get_type());
$fields = ["{$aggregationfieldsql} AS {$field['alias']}"];
} else {
$fields = array_map(static function(array $field): string {
return "{$field['sql']} AS {$field['alias']}";
}, $fieldsalias);
}
return array_values($fields);
}
/**
* Return column parameters, prefixed by the current index to allow the column to be added multiple times to a report
*
* @return array
*/
public function get_params(): array {
$params = [];
foreach ($this->params as $name => $value) {
$paramname = $this->unique_param_name($name);
$params[$paramname] = $value;
}
return $params;
}
/**
* Return an alias for this column (the generated alias of it's first field)
*
* @return string
* @throws coding_exception
*/
public function get_column_alias(): string {
if (!$fields = $this->get_fields_sql_alias()) {
throw new coding_exception('Column ' . $this->get_unique_identifier() . ' contains no fields');
}
return reset($fields)['alias'];
}
/**
* Define suitable SQL fragment for grouping by the columns fields. This will be returned from {@see get_groupby_sql} if set
*
* @param string $groupbysql
* @return self
*/
public function set_groupby_sql(string $groupbysql): self {
$this->groupbysql = $groupbysql;
return $this;
}
/**
* Return suitable SQL fragment for grouping by the column fields (during aggregation)
*
* @return array
*/
public function get_groupby_sql(): array {
global $DB;
// Return defined value if it's already been set during column definition.
if (!empty($this->groupbysql)) {
return [$this->groupbysql];
}
$fieldsalias = $this->get_fields_sql_alias();
// Note that we can reference field aliases in GROUP BY only in MySQL/Postgres.
$usealias = in_array($DB->get_dbfamily(), ['mysql', 'postgres']);
$columnname = $usealias ? 'alias' : 'sql';
return array_column($fieldsalias, $columnname);
}
/**
* Adds column callback (in the case there are multiple, they will be called iteratively - the result of each passed
* along to the next in the chain)
*
* The callback should implement the following signature (where $value is the first column field, $row is all column
* fields, $additionalarguments are those passed to this method, and $aggregation indicates the current aggregation type
* being applied to the column):
*
* function($value, stdClass $row, $additionalarguments, ?string $aggregation): string
*
* The type of the $value parameter passed to the callback is determined by calling {@see set_type}, this type is preserved
* if the column is part of a report source and is being aggregated. For entities that can be left joined to a report, the
* first argument of the callback must be nullable (as it should also be if the first column field is itself nullable).
*
* @param callable $callable
* @param mixed $additionalarguments
* @return self
*/
public function add_callback(callable $callable, $additionalarguments = null): self {
$this->callbacks[] = [$callable, $additionalarguments];
return $this;
}
/**
* Sets column callback. This will overwrite any previously added callbacks {@see add_callback}
*
* @param callable $callable
* @param mixed $additionalarguments
* @return self
*/
public function set_callback(callable $callable, $additionalarguments = null): self {
$this->callbacks = [];
return $this->add_callback($callable, $additionalarguments);
}
/**
* Set column aggregation type
*
* @param string|null $aggregation Type of aggregation, e.g. 'sum', 'count', etc
* @return self
* @throws coding_exception For invalid aggregation type, or one that is incompatible with column type
*/
public function set_aggregation(?string $aggregation): self {
if (!empty($aggregation)) {
$aggregation = aggregation::get_full_classpath($aggregation);
if (!aggregation::valid($aggregation) || !$aggregation::compatible($this->get_type())) {
throw new coding_exception('Invalid column aggregation', $aggregation);
}
}
$this->aggregation = $aggregation;
return $this;
}
/**
* Get column aggregation type
*
* @return base|null
*/
public function get_aggregation(): ?string {
return $this->aggregation;
}
/**
* Set disabled aggregation methods for the column. Typically only those methods suitable for the current column type are
* available: {@see aggregation::get_column_aggregations}, however in some cases we may want to disable specific methods
*
* @param array $disabledaggregation Array of types, e.g. ['min', 'sum']
* @return self
*/
public function set_disabled_aggregation(array $disabledaggregation): self {
$this->disabledaggregation = $disabledaggregation;
return $this;
}
/**
* Disable all aggregation methods for the column, for instance when current database can't aggregate fields that contain
* sub-queries
*
* @return self
*/
public function set_disabled_aggregation_all(): self {
$aggregationnames = array_map(static function(string $aggregation): string {
return $aggregation::get_class_name();
}, aggregation::get_aggregations());
return $this->set_disabled_aggregation($aggregationnames);
}
/**
* Return those aggregations methods explicitly disabled for the column
*
* @return array
*/
public function get_disabled_aggregation(): array {
return $this->disabledaggregation;
}
/**
* Sets the column as sortable
*
* @param bool $issortable
* @param array $sortfields Define the fields that should be used when the column is sorted, typically a subset of the fields
* selected for the column, via {@see add_field}. If omitted then the first selected field is used
* @return self
*/
public function set_is_sortable(bool $issortable, array $sortfields = []): self {
$this->issortable = $issortable;
$this->sortfields = $sortfields;
return $this;
}
/**
* Return sortable status of column
*
* @return bool
*/
public function get_is_sortable(): bool {
// Defer sortable status to aggregation type if column is being aggregated.
if (!empty($this->aggregation)) {
return $this->aggregation::sortable($this->issortable);
}
return $this->issortable;
}
/**
* Return fields to use for sorting of the column, where available the field aliases will be returned
*
* @return array
*/
public function get_sort_fields(): array {
$fieldsalias = $this->get_fields_sql_alias();
return array_map(static function(string $sortfield) use ($fieldsalias): string {
// Check whether sortfield refers to a defined field alias.
if (array_key_exists($sortfield, $fieldsalias)) {
return $fieldsalias[$sortfield]['alias'];
}
// Check whether sortfield refers to field SQL.
foreach ($fieldsalias as $field) {
if (strcasecmp($sortfield, $field['sql']) === 0) {
$sortfield = $field['alias'];
break;
}
}
return $sortfield;
}, $this->sortfields);
}
/**
* Extract all values from given row for this column
*
* @param array $row
* @return array
*/
private function get_values(array $row): array {
$values = [];
// During aggregation we only get a single alias back, subsequent aliases won't exist.
foreach ($this->get_fields_sql_alias() as $alias => $field) {
$values[$alias] = $row[$field['alias']] ?? null;
}
return $values;
}
/**
* Return the default column value, that being the value of it's first field
*
* @param array $values
* @param int $columntype
* @return mixed
*/
public static function get_default_value(array $values, int $columntype) {
$value = reset($values);
if ($value === null) {
return $value;
}
// Ensure default value is cast to it's strict type.
switch ($columntype) {
case self::TYPE_INTEGER:
case self::TYPE_TIMESTAMP:
$value = (int) $value;
break;
case self::TYPE_FLOAT:
$value = (float) $value;
break;
case self::TYPE_BOOLEAN:
$value = (bool) $value;
break;
}
return $value;
}
/**
* Return column value based on complete table row
*
* @param array $row
* @return mixed
*/
public function format_value(array $row) {
$values = $this->get_values($row);
$value = self::get_default_value($values, $this->type);
// If column is being aggregated then defer formatting to them, otherwise loop through all column callbacks.
if (!empty($this->aggregation)) {
$value = $this->aggregation::format_value($value, $values, $this->callbacks, $this->type);
} else {
foreach ($this->callbacks as $callback) {
[$callable, $arguments] = $callback;
$value = ($callable)($value, (object) $values, $arguments, null);
}
}
return $value;
}
/**
* Add column attributes (data-, class, etc.) that will be included in HTML when column is displayed
*
* @param array $attributes
* @return self
*/
public function add_attributes(array $attributes): self {
$this->attributes = $attributes + $this->attributes;
return $this;
}
/**
* Returns the column HTML attributes
*
* @return array
*/
public function get_attributes(): array {
return $this->attributes;
}
/**
* Return available state of the column for the current user. For instance the column may be added to a report with the
* expectation that only some users are able to see it
*
* @return bool
*/
public function get_is_available(): bool {
return $this->available;
}
/**
* Conditionally set whether the column is available.
*
* @param bool $available
* @return self
*/
public function set_is_available(bool $available): self {
$this->available = $available;
return $this;
}
/**
* Set deprecated state of the column, in which case it will still be shown when already present in existing reports but
* won't be available for selection in the report editor
*
* @param string $deprecatedmessage
* @return self
*/
public function set_is_deprecated(string $deprecatedmessage = ''): self {
$this->deprecated = true;
$this->deprecatedmessage = $deprecatedmessage;
return $this;
}
/**
* Return deprecated state of the column
*
* @return bool
*/
public function get_is_deprecated(): bool {
return $this->deprecated;
}
/**
* Return deprecated message of the column
*
* @return string
*/
public function get_is_deprecated_message(): string {
return $this->deprecatedmessage;
}
/**
* Set column persistent
*
* @param column_model $persistent
* @return self
*/
public function set_persistent(column_model $persistent): self {
$this->persistent = $persistent;
return $this;
}
/**
* Return column persistent
*
* @return mixed
*/
public function get_persistent(): column_model {
return $this->persistent;
}
}

Some files were not shown because too many files have changed in this diff Show More