mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00
MDL-70794 reportbuilder: management and further utility classes.
We define the base classes and APIs for reports, that can contain columns and filters instances themselves.
This commit is contained in:
parent
2a20238924
commit
93025be2e7
10 changed files with 1198 additions and 0 deletions
53
reportbuilder/classes/local/helpers/format.php
Normal file
53
reportbuilder/classes/local/helpers/format.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?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 format columns data as 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 $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 $value
|
||||
* @return string
|
||||
*/
|
||||
public static function boolean_as_text(bool $value): string {
|
||||
return $value ? get_string('yes') : get_string('no');
|
||||
}
|
||||
}
|
101
reportbuilder/classes/local/models/report.php
Normal file
101
reportbuilder/classes/local/models/report.php
Normal 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\local\models;
|
||||
|
||||
use context;
|
||||
use context_system;
|
||||
use core\persistent;
|
||||
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 [
|
||||
'source' => [
|
||||
'type' => PARAM_RAW,
|
||||
],
|
||||
'type' => [
|
||||
'type' => PARAM_INT,
|
||||
'choices' => [
|
||||
base::TYPE_CUSTOM_REPORT,
|
||||
base::TYPE_SYSTEM_REPORT,
|
||||
],
|
||||
],
|
||||
'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;
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report ID
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_id(): int {
|
||||
return (int) $this->raw_get('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report context, used by exporters
|
||||
*
|
||||
* @return context
|
||||
*/
|
||||
public function get_context(): context {
|
||||
return context::instance_by_id($this->raw_get('contextid'));
|
||||
}
|
||||
}
|
420
reportbuilder/classes/local/report/base.php
Normal file
420
reportbuilder/classes/local/report/base.php
Normal file
|
@ -0,0 +1,420 @@
|
|||
<?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\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 lang_string[] */
|
||||
private $entitytitles = [];
|
||||
|
||||
/** @var column[] $columns */
|
||||
private $columns = [];
|
||||
|
||||
/** @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 = '';
|
||||
|
||||
/**
|
||||
* Base report constructor
|
||||
*
|
||||
* @param report $report
|
||||
*/
|
||||
public function __construct(report $report) {
|
||||
$this->report = $report;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 clause that will always be applied 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 {
|
||||
database::validate_params($params);
|
||||
|
||||
$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,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (empty($name) || $name !== clean_param($name, PARAM_ALPHANUMEXT)) {
|
||||
throw new coding_exception('Entity name must be comprised of alphanumeric character, underscore or dash');
|
||||
}
|
||||
|
||||
$this->entitytitles[$name] = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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_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'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the report can be downloaded.
|
||||
*
|
||||
* @param bool $downloadable
|
||||
* @param string $downloadfilename If the report is downloadable, then a filename should be provided here
|
||||
*/
|
||||
final public function set_downloadable(bool $downloadable, string $downloadfilename = 'export'): void {
|
||||
$this->downloadable = $downloadable;
|
||||
$this->downloadfilename = $downloadfilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
87
reportbuilder/classes/manager.php
Normal file
87
reportbuilder/classes/manager.php
Normal 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;
|
||||
|
||||
use stdClass;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\base;
|
||||
|
||||
/**
|
||||
* Report management 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 manager {
|
||||
|
||||
/**
|
||||
* Return an instance of a report class from the given report persistent
|
||||
*
|
||||
* @param report $report
|
||||
* @param array $parameters
|
||||
* @return base
|
||||
* @throws source_invalid_exception
|
||||
* @throws source_unavailable_exception
|
||||
*/
|
||||
public static function get_report_from_persistent(report $report, array $parameters = []): base {
|
||||
$source = $report->get('source');
|
||||
|
||||
// Throw exception for invalid or unavailable report source.
|
||||
if (!self::report_source_exists($source)) {
|
||||
throw new source_invalid_exception($source);
|
||||
} else if (!self::report_source_available($source)) {
|
||||
throw new source_unavailable_exception($source);
|
||||
}
|
||||
|
||||
return new $source($report, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that report source exists and extends appropriate base classes
|
||||
*
|
||||
* @param string $source Full namespaced path to report definition
|
||||
* @param string $additionalbaseclass Specify addition base class that given classname should extend
|
||||
* @return bool
|
||||
*/
|
||||
public static function report_source_exists(string $source, string $additionalbaseclass = ''): bool {
|
||||
return (class_exists($source) && is_subclass_of($source, base::class) &&
|
||||
(empty($additionalbaseclass) || is_subclass_of($source, $additionalbaseclass)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify given report source is available. Note that it is assumed caller has already checked that it exists
|
||||
*
|
||||
* @param string $source
|
||||
* @return bool
|
||||
*/
|
||||
public static function report_source_available(string $source): bool {
|
||||
return call_user_func([$source, 'is_available']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new report persistent
|
||||
*
|
||||
* @param stdClass $reportdata
|
||||
* @return report
|
||||
*/
|
||||
public static function create_report_persistent(stdClass $reportdata): report {
|
||||
return (new report(0, $reportdata))->create();
|
||||
}
|
||||
}
|
38
reportbuilder/classes/report_access_exception.php
Normal file
38
reportbuilder/classes/report_access_exception.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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 moodle_exception;
|
||||
|
||||
/**
|
||||
* User cannot access report exception
|
||||
*
|
||||
* @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_access_exception extends moodle_exception {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct('errorreportaccess', 'reportbuilder');
|
||||
}
|
||||
}
|
40
reportbuilder/classes/source_invalid_exception.php
Normal file
40
reportbuilder/classes/source_invalid_exception.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?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 moodle_exception;
|
||||
|
||||
/**
|
||||
* Invalid report source exception
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class source_invalid_exception extends moodle_exception {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $source
|
||||
*/
|
||||
public function __construct(string $source) {
|
||||
parent::__construct('errorsourceinvalid', 'reportbuilder', '', null, $source);
|
||||
}
|
||||
}
|
40
reportbuilder/classes/source_unavailable_exception.php
Normal file
40
reportbuilder/classes/source_unavailable_exception.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?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 moodle_exception;
|
||||
|
||||
/**
|
||||
* Unavailable report source exception
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @copyright 2020 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class source_unavailable_exception extends moodle_exception {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $source
|
||||
*/
|
||||
public function __construct(string $source) {
|
||||
parent::__construct('errorsourceunavailable', 'reportbuilder', '', null, $source);
|
||||
}
|
||||
}
|
265
reportbuilder/classes/system_report.php
Normal file
265
reportbuilder/classes/system_report.php
Normal file
|
@ -0,0 +1,265 @@
|
|||
<?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 stdClass;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\action;
|
||||
use core_reportbuilder\local\report\base;
|
||||
use core_reportbuilder\local\report\column;
|
||||
|
||||
/**
|
||||
* Base class for system 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 system_report extends base {
|
||||
|
||||
/** @var array $parameters */
|
||||
private $parameters;
|
||||
|
||||
/** @var string[] $basefields List of base fields */
|
||||
private $basefields = [];
|
||||
|
||||
/** @var action[] $actions */
|
||||
private $actions = [];
|
||||
|
||||
/** @var column $initialsortcolumn */
|
||||
private $initialsortcolumn;
|
||||
|
||||
/** @var int $initialsortdirection */
|
||||
private $initialsortdirection;
|
||||
|
||||
/**
|
||||
* System report constructor.
|
||||
*
|
||||
* @param report $report
|
||||
* @param array $parameters
|
||||
*/
|
||||
final public function __construct(report $report, array $parameters) {
|
||||
$this->parameters = $parameters;
|
||||
|
||||
parent::__construct($report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates access to view this report
|
||||
*
|
||||
* This is necessary to implement independently of the page that would typically embed the report because
|
||||
* subsequent pages are requested via AJAX requests, and access should be validated each time
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function can_view(): bool;
|
||||
|
||||
/**
|
||||
* Validate access to the report
|
||||
*
|
||||
* @throws report_access_exception
|
||||
*/
|
||||
final public function require_can_view(): void {
|
||||
if (!$this->can_view()) {
|
||||
throw new report_access_exception();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report validation
|
||||
*
|
||||
* @throws report_access_exception If user cannot access the report
|
||||
* @throws coding_exception If no default column are specified
|
||||
*/
|
||||
protected function validate(): void {
|
||||
parent::validate();
|
||||
|
||||
$this->require_can_view();
|
||||
|
||||
// Ensure the report has some default columns specified.
|
||||
if (empty($this->get_columns())) {
|
||||
throw new coding_exception('No columns added');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add list of fields that have to be always included in SQL query for actions and row classes
|
||||
*
|
||||
* Base fields are only available in system reports because they are not compatible with aggregation
|
||||
*
|
||||
* @param string $sql SQL clause for the list of fields that only uses main table or base joins
|
||||
*/
|
||||
final protected function add_base_fields(string $sql): void {
|
||||
$this->basefields[] = $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report base fields
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
final public function get_base_fields(): array {
|
||||
return $this->basefields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an action to the report
|
||||
*
|
||||
* @param action $action
|
||||
*/
|
||||
final public function add_action(action $action): void {
|
||||
$this->actions[] = $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether report has any actions
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final public function has_actions(): bool {
|
||||
return !empty($this->actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return report actions
|
||||
*
|
||||
* @return action[]
|
||||
*/
|
||||
final public function get_actions(): array {
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all report parameters
|
||||
*
|
||||
* @param array $parameters
|
||||
*/
|
||||
final public function set_parameters(array $parameters): void {
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all report parameters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
final public function get_parameters(): array {
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return specific report parameter
|
||||
*
|
||||
* @param string $param
|
||||
* @param mixed $default
|
||||
* @param string $type
|
||||
* @return mixed
|
||||
*/
|
||||
final public function get_parameter(string $param, $default, string $type) {
|
||||
if (!array_key_exists($param, $this->parameters)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return clean_param($this->parameters[$param], $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS classes to add to the row. Can be overridden by system reports do define class to be added to output according to
|
||||
* content of each row
|
||||
*
|
||||
* @param stdClass $row
|
||||
* @return string
|
||||
*/
|
||||
public function get_row_class(stdClass $row): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default 'per page' size. Can be overridden by system reports to define a different paging value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_default_per_page(): int {
|
||||
return self::DEFAULT_PAGESIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before rendering each row. Can be overridden to pre-fetch/create objects and store them in the class, which can
|
||||
* later be used in column and action callbacks
|
||||
*
|
||||
* @param stdClass $row
|
||||
*/
|
||||
public function row_callback(stdClass $row): void {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates access to download this report.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
final public function can_be_downloaded(): bool {
|
||||
return $this->can_view() && $this->is_downloadable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of column names that will be excluded when table is downloaded. Extending classes should override this method
|
||||
* as appropriate
|
||||
*
|
||||
* @return string[] Array of column unique identifiers
|
||||
*/
|
||||
public function get_exclude_columns_for_download(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set initial sort column and sort direction for the report
|
||||
*
|
||||
* @param string $uniqueidentifier
|
||||
* @param int $sortdirection One of SORT_ASC or SORT_DESC
|
||||
* @throws coding_exception
|
||||
*/
|
||||
public function set_initial_sort_column(string $uniqueidentifier, int $sortdirection): void {
|
||||
if (!$sortcolumn = $this->get_column($uniqueidentifier)) {
|
||||
throw new coding_exception('Unknown column identifier', $uniqueidentifier);
|
||||
}
|
||||
|
||||
$this->initialsortcolumn = $sortcolumn;
|
||||
$this->initialsortdirection = $sortdirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get initial sort column
|
||||
*
|
||||
* @return column|null
|
||||
*/
|
||||
public function get_initial_sort_column(): ?column {
|
||||
return $this->initialsortcolumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get initial sort column direction
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_initial_sort_direction(): int {
|
||||
return $this->initialsortdirection;
|
||||
}
|
||||
}
|
87
reportbuilder/classes/system_report_factory.php
Normal file
87
reportbuilder/classes/system_report_factory.php
Normal 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;
|
||||
|
||||
use context;
|
||||
use core_reportbuilder\local\models\report;
|
||||
use core_reportbuilder\local\report\base;
|
||||
|
||||
/**
|
||||
* Factory class for creating system report instances
|
||||
*
|
||||
* @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_factory {
|
||||
|
||||
/**
|
||||
* Create and return instance of given system report source
|
||||
*
|
||||
* @param string $source Class path of system report definition
|
||||
* @param context $context
|
||||
* @param string $component
|
||||
* @param string $area
|
||||
* @param int $itemid
|
||||
* @param array $parameters Simple key/value pairs, accessed inside reports using $this->get_parameter()
|
||||
* @return system_report
|
||||
* @throws source_invalid_exception
|
||||
*/
|
||||
public static function create(string $source, context $context, string $component = '', string $area = '',
|
||||
int $itemid = 0, array $parameters = []): system_report {
|
||||
|
||||
// Exit early if source isn't a system report.
|
||||
if (!manager::report_source_exists($source, system_report::class)) {
|
||||
throw new source_invalid_exception($source);
|
||||
}
|
||||
|
||||
$report = static::get_report_persistent($source, $context, $component, $area, $itemid);
|
||||
|
||||
return manager::get_report_from_persistent($report, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a report source, with accompanying context information, return a persistent report instance
|
||||
*
|
||||
* @param string $source
|
||||
* @param context $context
|
||||
* @param string $component
|
||||
* @param string $area
|
||||
* @param int $itemid
|
||||
* @return report
|
||||
*/
|
||||
private static function get_report_persistent(string $source, context $context, string $component = '', string $area = '',
|
||||
int $itemid = 0): report {
|
||||
|
||||
$reportdata = [
|
||||
'type' => base::TYPE_SYSTEM_REPORT,
|
||||
'source' => $source,
|
||||
'contextid' => $context->id,
|
||||
'component' => $component,
|
||||
'area' => $area,
|
||||
'itemid' => $itemid,
|
||||
];
|
||||
|
||||
if ($report = report::get_record($reportdata)) {
|
||||
return $report;
|
||||
}
|
||||
|
||||
return manager::create_report_persistent((object) $reportdata);
|
||||
}
|
||||
}
|
67
reportbuilder/tests/local/helpers/format_test.php
Normal file
67
reportbuilder/tests/local/helpers/format_test.php
Normal file
|
@ -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\helpers;
|
||||
|
||||
use advanced_testcase;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Unit tests for the format helper
|
||||
*
|
||||
* @package core_reportbuilder
|
||||
* @covers \core_reportbuilder\local\helpers\format
|
||||
* @copyright 2021 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class format_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test userdate method
|
||||
*/
|
||||
public function test_userdate(): void {
|
||||
$now = time();
|
||||
|
||||
$userdate = format::userdate($now, new stdClass());
|
||||
$this->assertEquals(userdate($now), $userdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_boolean_as_text}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function boolean_as_text_provider(): array {
|
||||
return [
|
||||
[false, get_string('no')],
|
||||
[true, get_string('yes')],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test boolean as text
|
||||
*
|
||||
* @param bool $value
|
||||
* @param string $expected
|
||||
*
|
||||
* @dataProvider boolean_as_text_provider
|
||||
*/
|
||||
public function test_boolean_as_text(bool $value, string $expected): void {
|
||||
$this->assertEquals($expected, format::boolean_as_text($value));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue