From 95967d62d56834ed77799735d25e50583d8a9359 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Mon, 5 Jul 2021 11:51:20 +0100 Subject: [PATCH] MDL-70795 reportbuilder: define updated schema for custom reports. Define new filter/column models, and updated database schema to match. --- lang/en/reportbuilder.php | 8 + lib/db/install.xml | 47 +++++- lib/db/upgrade.php | 74 +++++++++ reportbuilder/classes/local/models/column.php | 122 ++++++++++++++ reportbuilder/classes/local/models/filter.php | 155 ++++++++++++++++++ reportbuilder/classes/local/models/report.php | 19 +++ reportbuilder/classes/privacy/provider.php | 14 ++ reportbuilder/tests/privacy/provider_test.php | 18 +- version.php | 2 +- 9 files changed, 453 insertions(+), 6 deletions(-) create mode 100644 reportbuilder/classes/local/models/column.php create mode 100644 reportbuilder/classes/local/models/filter.php diff --git a/lang/en/reportbuilder.php b/lang/en/reportbuilder.php index 1acd8d36e07..54b9874263b 100644 --- a/lang/en/reportbuilder.php +++ b/lang/en/reportbuilder.php @@ -66,6 +66,14 @@ $string['filtersapplied'] = 'Filters applied'; $string['filtersappliedx'] = 'Filters ({$a})'; $string['filtersreset'] = 'Filters reset'; $string['filterstartswith'] = 'Starts with'; +$string['privacy:metadata:column'] = 'Report column definitions'; +$string['privacy:metadata:column:uniqueidentifier'] = 'Unique identifier of the column'; +$string['privacy:metadata:column:usercreated'] = 'The ID of the user who created the column'; +$string['privacy:metadata:column:usermodified'] = 'The ID of the user who last modified the column'; +$string['privacy:metadata:filter'] = 'Report filter definitions'; +$string['privacy:metadata:filter:uniqueidentifier'] = 'Unique identifier of the filter'; +$string['privacy:metadata:filter:usercreated'] = 'The ID of the user who created the filter'; +$string['privacy:metadata:filter:usermodified'] = 'The ID of the user who last modified the filter'; $string['privacy:metadata:preference:reportfilter'] = 'Stored report filter values'; $string['privacy:metadata:report'] = 'Report definitions'; $string['privacy:metadata:report:name'] = 'The name of the report'; diff --git a/lib/db/install.xml b/lib/db/install.xml index 51edcd67aba..67d944175f8 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -4382,8 +4382,10 @@ + + @@ -4400,5 +4402,48 @@
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 43cbaa688f4..9039e9d2163 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2936,5 +2936,79 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2021100600.04); } + if ($oldversion < 2021101900.01) { + $table = new xmldb_table('reportbuilder_report'); + + // Define field name to be added to reportbuilder_report. + $field = new xmldb_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'id'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field conditiondata to be added to reportbuilder_report. + $field = new xmldb_field('conditiondata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'type'); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define table reportbuilder_column to be created. + $table = new xmldb_table('reportbuilder_column'); + + // Adding fields to table reportbuilder_column. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('uniqueidentifier', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('aggregation', XMLDB_TYPE_CHAR, '32', null, null, null, null); + $table->add_field('heading', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('columnorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('sortenabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('sortdirection', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null); + $table->add_field('sortorder', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + + // Adding keys to table reportbuilder_column. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + $table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']); + $table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']); + $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']); + + // Conditionally launch create table for reportbuilder_column. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table reportbuilder_filter to be created. + $table = new xmldb_table('reportbuilder_filter'); + + // Adding fields to table reportbuilder_filter. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('uniqueidentifier', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('heading', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('iscondition', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('filterorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + + // Adding keys to table reportbuilder_filter. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + $table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']); + $table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']); + $table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']); + + // Conditionally launch create table for reportbuilder_filter. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2021101900.01); + } + return true; } diff --git a/reportbuilder/classes/local/models/column.php b/reportbuilder/classes/local/models/column.php new file mode 100644 index 00000000000..a4a4bf29825 --- /dev/null +++ b/reportbuilder/classes/local/models/column.php @@ -0,0 +1,122 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\models; + +use lang_string; +use core\persistent; + +/** + * Persistent class to represent a report column + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @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')); + } + + /** + * 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); + } +} diff --git a/reportbuilder/classes/local/models/filter.php b/reportbuilder/classes/local/models/filter.php new file mode 100644 index 00000000000..82d47230239 --- /dev/null +++ b/reportbuilder/classes/local/models/filter.php @@ -0,0 +1,155 @@ +. + +declare(strict_types=1); + +namespace core_reportbuilder\local\models; + +use lang_string; +use core\persistent; + +/** + * Persistent class to represent a report filter/condition + * + * @package core_reportbuilder + * @copyright 2021 Paul Holden + * @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; + } + + /** + * 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); + } +} diff --git a/reportbuilder/classes/local/models/report.php b/reportbuilder/classes/local/models/report.php index c0c03d31d06..2d0a65d9add 100644 --- a/reportbuilder/classes/local/models/report.php +++ b/reportbuilder/classes/local/models/report.php @@ -42,6 +42,11 @@ class report extends persistent { */ protected static function define_properties(): array { return [ + 'name' => [ + 'type' => PARAM_TEXT, + 'null' => NULL_ALLOWED, + 'default' => null, + ], 'source' => [ 'type' => PARAM_RAW, ], @@ -52,6 +57,11 @@ class report extends persistent { base::TYPE_SYSTEM_REPORT, ], ], + 'conditiondata' => [ + 'type' => PARAM_RAW, + 'null' => NULL_ALLOWED, + 'default' => null, + ], 'contextid' => [ 'type' => PARAM_INT, 'default' => static function(): int { @@ -89,4 +99,13 @@ class report extends persistent { 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]); + } } diff --git a/reportbuilder/classes/privacy/provider.php b/reportbuilder/classes/privacy/provider.php index a260154e716..2e49bcd0c23 100644 --- a/reportbuilder/classes/privacy/provider.php +++ b/reportbuilder/classes/privacy/provider.php @@ -21,7 +21,9 @@ namespace core_reportbuilder\privacy; use core_privacy\local\metadata\collection; use core_privacy\local\request\writer; use core_reportbuilder\local\helpers\user_filter_manager; +use core_reportbuilder\local\models\column; use core_reportbuilder\local\models\report; +use core_reportbuilder\local\models\filter; /** * Privacy Subsystem for core_reportbuilder @@ -47,6 +49,18 @@ class provider implements 'usermodified' => 'privacy:metadata:report:usermodified', ], 'privacy:metadata:report'); + $collection->add_database_table(column::TABLE, [ + 'uniqueidentifier' => 'privacy:metadata:column:uniqueidentifier', + 'usercreated' => 'privacy:metadata:column:usercreated', + 'usermodified' => 'privacy:metadata:column:usermodified', + ], 'privacy:metadata:column'); + + $collection->add_database_table(filter::TABLE, [ + 'uniqueidentifier' => 'privacy:metadata:filter:uniqueidentifier', + 'usercreated' => 'privacy:metadata:filter:usercreated', + 'usermodified' => 'privacy:metadata:filter:usermodified', + ], 'privacy:metadata:filter'); + $collection->add_user_preference('core_reportbuilder', 'privacy:metadata:preference:reportfilter'); return $collection; diff --git a/reportbuilder/tests/privacy/provider_test.php b/reportbuilder/tests/privacy/provider_test.php index 34f54b9212b..bf18dd73ebe 100644 --- a/reportbuilder/tests/privacy/provider_test.php +++ b/reportbuilder/tests/privacy/provider_test.php @@ -23,8 +23,12 @@ use core_privacy\local\metadata\collection; use core_privacy\local\metadata\types\database_table; use core_privacy\local\metadata\types\user_preference; use core_privacy\local\request\writer; +use core_privacy\tests\provider_testcase; use core_reportbuilder\manager; use core_reportbuilder\local\helpers\user_filter_manager; +use core_reportbuilder\local\models\column; +use core_reportbuilder\local\models\filter; +use core_reportbuilder\local\models\report; /** * Unit tests for privacy provider @@ -34,7 +38,7 @@ use core_reportbuilder\local\helpers\user_filter_manager; * @copyright 2021 David Matamoros * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider_testcase extends \core_privacy\tests\provider_testcase { +class provider_test extends provider_testcase { /** * Test provider metadata @@ -43,12 +47,18 @@ class provider_testcase extends \core_privacy\tests\provider_testcase { $collection = new collection('core_reportbuilder'); $metadata = provider::get_metadata($collection)->get_collection(); - $this->assertCount(2, $metadata); + $this->assertCount(4, $metadata); $this->assertInstanceOf(database_table::class, $metadata[0]); - $this->assertEquals('reportbuilder_report', $metadata[0]->get_name()); + $this->assertEquals(report::TABLE, $metadata[0]->get_name()); - $this->assertInstanceOf(user_preference::class, $metadata[1]); + $this->assertInstanceOf(database_table::class, $metadata[1]); + $this->assertEquals(column::TABLE, $metadata[1]->get_name()); + + $this->assertInstanceOf(database_table::class, $metadata[2]); + $this->assertEquals(filter::TABLE, $metadata[2]->get_name()); + + $this->assertInstanceOf(user_preference::class, $metadata[3]); } /** diff --git a/version.php b/version.php index 49a05533dac..c8778b1e5b4 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2021101900.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2021101900.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.0dev+ (Build: 20211019)'; // Human-friendly version name