diff --git a/comment/classes/reportbuilder/datasource/comments.php b/comment/classes/reportbuilder/datasource/comments.php index 900c4d7d8e3..b8f77310c4a 100644 --- a/comment/classes/reportbuilder/datasource/comments.php +++ b/comment/classes/reportbuilder/datasource/comments.php @@ -18,6 +18,7 @@ declare(strict_types=1); namespace core_comment\reportbuilder\datasource; +use core\reportbuilder\local\entities\context; use core_reportbuilder\datasource; use core_reportbuilder\local\entities\user; use core_comment\reportbuilder\local\entities\comment; @@ -48,9 +49,15 @@ class comments extends datasource { $commentalias = $commententity->get_table_alias('comments'); $this->set_main_table('comments', $commentalias); - $this->add_entity($commententity); + // Join the context entity. + $contextentity = (new context()) + ->set_table_alias('context', $commententity->get_table_alias('context')); + $this->add_entity($contextentity + ->add_join($commententity->get_context_join()) + ); + // Join the user entity to the comment userid (author). $userentity = new user(); $useralias = $userentity->get_table_alias('user'); @@ -68,7 +75,7 @@ class comments extends datasource { */ public function get_default_columns(): array { return [ - 'comment:context', + 'context:name', 'comment:content', 'user:fullname', 'comment:timecreated', diff --git a/comment/classes/reportbuilder/local/entities/comment.php b/comment/classes/reportbuilder/local/entities/comment.php index a231dfe8755..f32816c59c5 100644 --- a/comment/classes/reportbuilder/local/entities/comment.php +++ b/comment/classes/reportbuilder/local/entities/comment.php @@ -103,7 +103,7 @@ class comment extends base { )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_LONGTEXT) - ->add_join("LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$commentalias}.contextid") + ->add_join($this->get_context_join()) ->add_field($contentfieldsql, 'content') ->add_fields("{$commentalias}.format, {$commentalias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias)) @@ -126,10 +126,11 @@ class comment extends base { )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TEXT) - ->add_join("LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$commentalias}.contextid") + ->add_join($this->get_context_join()) ->add_fields("{$commentalias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias)) // Sorting may not order alphabetically, but will at least group contexts together. ->set_is_sortable(true) + ->set_is_deprecated('See \'context:name\' for replacement') ->add_callback(static function($contextid, stdClass $context): string { if ($contextid === null) { return ''; @@ -147,10 +148,11 @@ class comment extends base { )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TEXT) - ->add_join("LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$commentalias}.contextid") + ->add_join($this->get_context_join()) ->add_fields("{$commentalias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias)) // Sorting may not order alphabetically, but will at least group contexts together. ->set_is_sortable(true) + ->set_is_deprecated('See \'context:link\' for replacement') ->add_callback(static function($contextid, stdClass $context): string { if ($contextid === null) { return ''; @@ -246,4 +248,16 @@ class comment extends base { return $filters; } + + /** + * Return syntax for joining on the context table + * + * @return string + */ + public function get_context_join(): string { + $commentalias = $this->get_table_alias('comments'); + $contextalias = $this->get_table_alias('context'); + + return "LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$commentalias}.contextid"; + } } diff --git a/comment/classes/reportbuilder/local/systemreports/comments.php b/comment/classes/reportbuilder/local/systemreports/comments.php index 0e94123212e..da41670e6f8 100644 --- a/comment/classes/reportbuilder/local/systemreports/comments.php +++ b/comment/classes/reportbuilder/local/systemreports/comments.php @@ -23,6 +23,7 @@ use lang_string; use moodle_url; use pix_icon; use stdClass; +use core\reportbuilder\local\entities\context; use core_reportbuilder\system_report; use core_reportbuilder\local\entities\user; use core_reportbuilder\local\report\action; @@ -53,6 +54,13 @@ class comments extends system_report { return [$row->id, get_string('select')]; }); + // Join the context entity. + $contextentity = (new context()) + ->set_table_alias('context', $commententity->get_table_alias('context')); + $this->add_entity($contextentity + ->add_join($commententity->get_context_join()) + ); + // Join the user entity to the comment userid (author). $userentity = new user(); $useralias = $userentity->get_table_alias('user'); @@ -82,7 +90,7 @@ class comments extends system_report { $this->add_columns_from_entities([ 'user:fullnamewithlink', 'comment:content', - 'comment:contexturl', + 'context:link', 'comment:timecreated', ]); diff --git a/comment/tests/reportbuilder/datasource/comments_test.php b/comment/tests/reportbuilder/datasource/comments_test.php index 8921ea23842..42ef3d4d147 100644 --- a/comment/tests/reportbuilder/datasource/comments_test.php +++ b/comment/tests/reportbuilder/datasource/comments_test.php @@ -22,7 +22,7 @@ use context_course; use core_comment_generator; use core_reportbuilder_generator; use core_reportbuilder_testcase; -use core_reportbuilder\local\filters\{date, text}; +use core_reportbuilder\local\filters\{date, select, text}; defined('MOODLE_INTERNAL') || die(); @@ -98,7 +98,7 @@ class comments_test extends core_reportbuilder_testcase { $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); $report = $generator->create_report(['name' => 'Blogs', 'source' => comments::class, 'default' => 0]); - $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'comment:contexturl']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:link']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'comment:component']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'comment:area']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'comment:itemid']); @@ -139,7 +139,17 @@ class comments_test extends core_reportbuilder_testcase { 'comment:timecreated_to' => 1622502000, ], false], - // User (just to check the join). + // Context. + 'Context level' => ['context:level', [ + 'context:level_operator' => select::EQUAL_TO, + 'context:level_value' => CONTEXT_COURSE, + ], true], + 'Context level (no match)' => ['context:level', [ + 'context:level_operator' => select::EQUAL_TO, + 'context:level_value' => CONTEXT_BLOCK, + ], false], + + // User. 'Filter user' => ['user:username', [ 'user:username_operator' => text::IS_EQUAL_TO, 'user:username_value' => 'admin', diff --git a/files/classes/reportbuilder/datasource/files.php b/files/classes/reportbuilder/datasource/files.php index ddfa4370785..e001da4a885 100644 --- a/files/classes/reportbuilder/datasource/files.php +++ b/files/classes/reportbuilder/datasource/files.php @@ -18,6 +18,7 @@ declare(strict_types=1); namespace core_files\reportbuilder\datasource; +use core\reportbuilder\local\entities\context; use core_files\reportbuilder\local\entities\file; use core_reportbuilder\datasource; use core_reportbuilder\local\entities\user; @@ -51,6 +52,13 @@ class files extends datasource { $this->set_main_table('files', $filesalias); $this->add_entity($fileentity); + // Join the context entity. + $contextentity = new context(); + $contextalias = $contextentity->get_table_alias('context'); + $this->add_entity($contextentity + ->add_join("LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$filesalias}.contextid") + ); + // Join the user entity. $userentity = new user(); $useralias = $userentity->get_table_alias('user'); @@ -69,7 +77,7 @@ class files extends datasource { */ public function get_default_columns(): array { return [ - 'file:context', + 'context:name', 'user:fullname', 'file:name', 'file:type', diff --git a/files/classes/reportbuilder/local/entities/file.php b/files/classes/reportbuilder/local/entities/file.php index 0b2dbc0616a..180bb4cacc9 100644 --- a/files/classes/reportbuilder/local/entities/file.php +++ b/files/classes/reportbuilder/local/entities/file.php @@ -200,6 +200,7 @@ class file extends base { ->add_fields("{$filesalias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias)) // Sorting may not order alphabetically, but will at least group contexts together. ->set_is_sortable(true) + ->set_is_deprecated('See \'context:name\' for replacement') ->add_callback(static function($contextid, stdClass $context): string { if ($contextid === null) { return ''; @@ -221,6 +222,7 @@ class file extends base { ->add_fields("{$filesalias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias)) // Sorting may not order alphabetically, but will at least group contexts together. ->set_is_sortable(true) + ->set_is_deprecated('See \'context:link\' for replacement') ->add_callback(static function($contextid, stdClass $context): string { if ($contextid === null) { return ''; diff --git a/files/tests/reportbuilder/datasource/files_test.php b/files/tests/reportbuilder/datasource/files_test.php index e990a55e434..9550ab516b5 100644 --- a/files/tests/reportbuilder/datasource/files_test.php +++ b/files/tests/reportbuilder/datasource/files_test.php @@ -62,13 +62,13 @@ class files_test extends core_reportbuilder_testcase { $content = $this->get_custom_report_content($report->get('id')); $content = $this->filter_custom_report_content($content, static function(array $row): bool { - return $row['c0_contextid'] !== 'System'; + return $row['c0_ctxid'] !== 'System'; }); $this->assertCount(2, $content); // Consistent order (course, user), just in case. - core_collator::asort_array_of_arrays_by_key($content, 'c0_contextid'); + core_collator::asort_array_of_arrays_by_key($content, 'c0_ctxid'); $content = array_values($content); // First row (course summary file). @@ -114,8 +114,10 @@ class files_test extends core_reportbuilder_testcase { $report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 0]); // Consistent order, sorted by context and content hash. - $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:contexturl', + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:link', 'sortenabled' => 1, 'sortorder' => 1]); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:name']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:level']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:path']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:author']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:license']); @@ -127,13 +129,15 @@ class files_test extends core_reportbuilder_testcase { $content = $this->get_custom_report_content($report->get('id')); $content = $this->filter_custom_report_content($content, static function(array $row): bool { - return stripos($row['c0_contextid'], 'System') === false; + return stripos($row['c0_ctxid'], 'System') === false; }); // There should be two entries (directory & file) for each context. $this->assertEquals([ [ "get_url()}\">{$coursecontext->get_context_name()}", + $coursecontext->get_context_name(), + 'Course', '/', null, '', @@ -144,6 +148,8 @@ class files_test extends core_reportbuilder_testcase { ], [ "get_url()}\">{$coursecontext->get_context_name()}", + $coursecontext->get_context_name(), + 'Course', '/', null, '', @@ -154,6 +160,8 @@ class files_test extends core_reportbuilder_testcase { ], [ "get_url()}\">{$usercontext->get_context_name()}", + $usercontext->get_context_name(), + 'User', '/', null, '', @@ -164,6 +172,8 @@ class files_test extends core_reportbuilder_testcase { ], [ "get_url()}\">{$usercontext->get_context_name()}", + $usercontext->get_context_name(), + 'User', '/', null, '', @@ -222,7 +232,17 @@ class files_test extends core_reportbuilder_testcase { 'file:timecreated_to' => 1622502000, ], 0], - // User (just to check the join). + // Context. + 'Context level' => ['context:level', [ + 'context:level_operator' => select::EQUAL_TO, + 'context:level_value' => CONTEXT_COURSE, + ], 2], + 'Context level (no match)' => ['context:level', [ + 'context:level_operator' => select::EQUAL_TO, + 'context:level_value' => CONTEXT_BLOCK, + ], 0], + + // User. 'Filter user' => ['user:username', [ 'user:username_operator' => text::IS_EQUAL_TO, 'user:username_value' => 'alfie', @@ -264,13 +284,13 @@ class files_test extends core_reportbuilder_testcase { // Create report containing single column, and given filter. $report = $generator->create_report(['name' => 'Files', 'source' => files::class, 'default' => 0]); - $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'file:context']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:name']); // Add filter, set it's values. $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]); $content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues); $content = $this->filter_custom_report_content($content, static function(array $row): bool { - return stripos($row['c0_contextid'], 'System') === false; + return stripos($row['c0_ctxid'], 'System') === false; }); $this->assertCount($expectmatchcount, $content); diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 7e70bb7dd4b..685e71933b5 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -299,6 +299,8 @@ $string['contentexport_modulesummary'] = 'This page is part of the content downl $string['contentexport_viewfilename'] = 'View the file {$a}'; $string['contentbank'] = 'Content bank'; $string['context'] = 'Context'; +$string['contextlevel'] = 'Context level'; +$string['contextname'] = 'Context name'; $string['contexturl'] = 'Context URL'; $string['continue'] = 'Continue'; $string['continuetocourse'] = 'Click here to enter your course'; diff --git a/lib/classes/reportbuilder/local/entities/context.php b/lib/classes/reportbuilder/local/entities/context.php new file mode 100644 index 00000000000..c9870150ee4 --- /dev/null +++ b/lib/classes/reportbuilder/local/entities/context.php @@ -0,0 +1,178 @@ +. + +declare(strict_types=1); + +namespace core\reportbuilder\local\entities; + +use core\context_helper; +use core_reportbuilder\local\entities\base; +use core_reportbuilder\local\filters\select; +use core_reportbuilder\local\report\{column, filter}; +use html_writer; +use lang_string; +use stdClass; + +/** + * Context entity + * + * @package core + * @copyright 2023 Paul Holden + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class context extends base { + + /** + * Database tables that this entity uses and their default aliases + * + * @return array + */ + protected function get_default_table_aliases(): array { + return ['context' => 'ctx']; + } + + /** + * The default title for this entity in the list of columns/conditions/filters in the report builder + * + * @return lang_string + */ + protected function get_default_entity_title(): lang_string { + return new lang_string('context'); + } + + /** + * Initialise the entity + * + * @return base + */ + public function initialise(): base { + $columns = $this->get_all_columns(); + foreach ($columns as $column) { + $this->add_column($column); + } + + // All the filters defined by the entity can also be used as conditions. + $filters = $this->get_all_filters(); + foreach ($filters as $filter) { + $this + ->add_filter($filter) + ->add_condition($filter); + } + + return $this; + } + + /** + * Returns list of all available columns + * + * @return column[] + */ + protected function get_all_columns(): array { + $contextalias = $this->get_table_alias('context'); + + // Name. + $columns[] = (new column( + 'name', + new lang_string('contextname'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_fields(context_helper::get_preload_record_columns_sql($contextalias)) + // Sorting may not order alphabetically, but will at least group contexts together. + ->set_is_sortable(true) + ->add_callback(static function($contextid, stdClass $context): string { + if ($contextid === null) { + return ''; + } + + context_helper::preload_from_record($context); + return context_helper::instance_by_id($contextid)->get_context_name(); + }); + + // Link. + $columns[] = (new column( + 'link', + new lang_string('contexturl'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_fields(context_helper::get_preload_record_columns_sql($contextalias)) + // Sorting may not order alphabetically, but will at least group contexts together. + ->set_is_sortable(true) + ->add_callback(static function($contextid, stdClass $context): string { + if ($contextid === null) { + return ''; + } + + context_helper::preload_from_record($context); + $context = context_helper::instance_by_id($contextid); + + return html_writer::link($context->get_url(), $context->get_context_name()); + }); + + // Level. + $columns[] = (new column( + 'level', + new lang_string('contextlevel'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_INTEGER) + ->add_fields("{$contextalias}.contextlevel") + ->set_is_sortable(true) + // It doesn't make sense to offer integer aggregation methods for this column. + ->set_disabled_aggregation(['avg', 'max', 'min', 'sum']) + ->add_callback(static function(?int $level): string { + if ($level === null) { + return ''; + } + + return context_helper::get_level_name($level); + }); + + return $columns; + } + + /** + * Return list of all available filters + * + * @return filter[] + */ + protected function get_all_filters(): array { + $contextalias = $this->get_table_alias('context'); + + // Level. + $filters[] = (new filter( + select::class, + 'level', + new lang_string('contextlevel'), + $this->get_entity_name(), + "{$contextalias}.contextlevel" + )) + ->add_joins($this->get_joins()) + ->set_options_callback(static function(): array { + $levels = context_helper::get_all_levels(); + + return array_map(static function(string $levelclass): string { + return $levelclass::get_level_name(); + }, $levels); + }); + + return $filters; + } +} diff --git a/reportbuilder/upgrade.txt b/reportbuilder/upgrade.txt index 20f7ad6cb83..cae745a29b0 100644 --- a/reportbuilder/upgrade.txt +++ b/reportbuilder/upgrade.txt @@ -7,8 +7,14 @@ Information provided here is intended especially for developers. * New `get_tag_joins_for_entity` helper in base entity class, for returning SQL joins necessary for retrieving tags * New `set_is_deprecated` method in base `local\report\[column|filter]` classes to deprecate report entity columns and filters * The following report entity columns have been deprecated, with replacements as follows: + - `comment:context` => `context:name` + - `comment:contexturl` => `context:link` - `enrolment:method` => `enrol:name` (plus enrolment formatter `enrolment_name` method) - 'enrolment:role` => `role:name` + - `file:context` => `context:name` + - `file:contexturl` => `context:link` + - `instance:context` (tag) => `context:name` + - `instance:contexturl` (tag) => `context:link` * The following report entity filters/conditions have been deprecated, with replacements as follows: - `enrolment:method` => `enrol:plugin` * Trying to add/annotate duplicate entity names to a report will now throw a coding exception diff --git a/tag/classes/reportbuilder/datasource/tags.php b/tag/classes/reportbuilder/datasource/tags.php index 18b693bec23..df9d4577203 100644 --- a/tag/classes/reportbuilder/datasource/tags.php +++ b/tag/classes/reportbuilder/datasource/tags.php @@ -19,6 +19,7 @@ declare(strict_types=1); namespace core_tag\reportbuilder\datasource; use lang_string; +use core\reportbuilder\local\entities\context; use core_reportbuilder\datasource; use core_reportbuilder\local\entities\user; use core_tag\reportbuilder\local\entities\{collection, tag, instance}; @@ -46,10 +47,9 @@ class tags extends datasource { */ protected function initialise(): void { $collectionentity = new collection(); - $collectionalias = $collectionentity->get_table_alias('tag_coll'); - $this->set_main_table('tag_coll', $collectionalias); + $this->set_main_table('tag_coll', $collectionalias); $this->add_entity($collectionentity); // Join tag entity to collection. @@ -67,6 +67,14 @@ class tags extends datasource { ->add_join("LEFT JOIN {tag_instance} {$instancealias} ON {$instancealias}.tagid = {$tagalias}.id") ); + // Join the context entity. + $contextentity = new context(); + $contextalias = $contextentity->get_table_alias('context'); + $this->add_entity($contextentity + ->add_joins($instanceentity->get_joins()) + ->add_join("LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$instancealias}.contextid") + ); + // Join user entity to represent the tag author. $userentity = (new user()) ->set_entity_title(new lang_string('tagauthor', 'core_tag')); @@ -90,7 +98,7 @@ class tags extends datasource { 'collection:name', 'tag:namewithlink', 'tag:standard', - 'instance:context', + 'context:name', ]; } diff --git a/tag/classes/reportbuilder/local/entities/instance.php b/tag/classes/reportbuilder/local/entities/instance.php index 818d51554d6..d1e89cd44cf 100644 --- a/tag/classes/reportbuilder/local/entities/instance.php +++ b/tag/classes/reportbuilder/local/entities/instance.php @@ -121,6 +121,7 @@ class instance extends base { ->add_fields("{$instancealias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias)) // Sorting may not order alphabetically, but will at least group contexts together. ->set_is_sortable(true) + ->set_is_deprecated('See \'context:name\' for replacement') ->add_callback(static function($contextid, stdClass $context): string { if ($contextid === null) { return ''; @@ -142,6 +143,7 @@ class instance extends base { ->add_fields("{$instancealias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias)) // Sorting may not order alphabetically, but will at least group contexts together. ->set_is_sortable(true) + ->set_is_deprecated('See \'context:link\' for replacement') ->add_callback(static function($contextid, stdClass $context): string { if ($contextid === null) { return ''; diff --git a/tag/tests/reportbuilder/datasource/tags_test.php b/tag/tests/reportbuilder/datasource/tags_test.php index 5aebcb762bb..759ed5551dc 100644 --- a/tag/tests/reportbuilder/datasource/tags_test.php +++ b/tag/tests/reportbuilder/datasource/tags_test.php @@ -61,7 +61,7 @@ class tags_test extends core_reportbuilder_testcase { $this->assertCount(2, $content); // Consistent order (course, user), just in case. - core_collator::asort_array_of_arrays_by_key($content, 'c3_contextid'); + core_collator::asort_array_of_arrays_by_key($content, 'c3_ctxid'); $content = array_values($content); // Default columns are collection, tag name, tag standard, instance context. @@ -103,8 +103,10 @@ class tags_test extends core_reportbuilder_testcase { $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:flagged']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:timemodified']); + // Context. + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:link']); + // Instance. - $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:contexturl']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:area']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:component']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'instance:itemtype']);