From ce1db1499d89b6b5381092b4c85e8e0d478a1b99 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Thu, 20 Jul 2023 17:53:35 +0100 Subject: [PATCH] MDL-78741 files: context report entity data for path and parent. Allows for better aggregation/grouping of file data by context tree. --- .../reportbuilder/datasource/files_test.php | 45 +++++++++++---- lang/en/moodle.php | 1 + .../reportbuilder/local/entities/context.php | 57 ++++++++++++++++++- 3 files changed, 90 insertions(+), 13 deletions(-) diff --git a/files/tests/reportbuilder/datasource/files_test.php b/files/tests/reportbuilder/datasource/files_test.php index 0c70e7d678a..f355e99712e 100644 --- a/files/tests/reportbuilder/datasource/files_test.php +++ b/files/tests/reportbuilder/datasource/files_test.php @@ -18,8 +18,7 @@ declare(strict_types=1); namespace core_files\reportbuilder\datasource; -use context_course; -use context_user; +use core\context\{course, coursecat, user}; use core_reportbuilder_generator; use core_reportbuilder_testcase; use core_reportbuilder\local\filters\{boolean_select, date, number, select, text}; @@ -46,10 +45,10 @@ class files_test extends core_reportbuilder_testcase { $this->resetAfterTest(); $course = $this->getDataGenerator()->create_course(); - $coursecontext = context_course::instance($course->id); + $coursecontext = course::instance($course->id); $user = $this->getDataGenerator()->create_user(); - $usercontext = context_user::instance($user->id); + $usercontext = user::instance($user->id); $this->setUser($user); @@ -91,11 +90,14 @@ class files_test extends core_reportbuilder_testcase { $this->resetAfterTest(); $this->setAdminUser(); - $course = $this->getDataGenerator()->create_course(); - $coursecontext = context_course::instance($course->id); + $category = $this->getDataGenerator()->create_category(); + $categorycontext = coursecat::instance($category->id); + + $course = $this->getDataGenerator()->create_course(['category' => $category->id]); + $coursecontext = course::instance($course->id); $user = $this->getDataGenerator()->create_user(); - $usercontext = context_user::instance($user->id); + $usercontext = user::instance($user->id); $this->setUser($user); @@ -110,6 +112,9 @@ class files_test extends core_reportbuilder_testcase { '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' => 'context:path']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'context:parent']); + $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']); @@ -130,6 +135,8 @@ class files_test extends core_reportbuilder_testcase { "get_url()}\">{$coursecontext->get_context_name()}", $coursecontext->get_context_name(), 'Course', + $coursecontext->path, + $categorycontext->get_context_name(), '/', null, '', @@ -142,6 +149,8 @@ class files_test extends core_reportbuilder_testcase { "get_url()}\">{$coursecontext->get_context_name()}", $coursecontext->get_context_name(), 'Course', + $coursecontext->path, + $categorycontext->get_context_name(), '/', null, '', @@ -154,6 +163,8 @@ class files_test extends core_reportbuilder_testcase { "get_url()}\">{$usercontext->get_context_name()}", $usercontext->get_context_name(), 'User', + $usercontext->path, + 'System', '/', null, '', @@ -166,6 +177,8 @@ class files_test extends core_reportbuilder_testcase { "get_url()}\">{$usercontext->get_context_name()}", $usercontext->get_context_name(), 'User', + $usercontext->path, + 'System', '/', null, '', @@ -233,6 +246,14 @@ class files_test extends core_reportbuilder_testcase { 'context:level_operator' => select::EQUAL_TO, 'context:level_value' => CONTEXT_BLOCK, ], 0], + 'Context path' => ['context:path', [ + 'context:path_operator' => text::STARTS_WITH, + 'context:path_value' => '/1/', + ], 4], + 'Context path (no match)' => ['context:path', [ + 'context:path_operator' => text::STARTS_WITH, + 'context:path_value' => '/1/2/3/', + ], 0], // User. 'Filter user' => ['user:username', [ @@ -267,7 +288,7 @@ class files_test extends core_reportbuilder_testcase { $this->setUser($user); $course = $this->getDataGenerator()->create_course(); - $coursecontext = context_course::instance($course->id); + $coursecontext = course::instance($course->id); $this->generate_test_files($coursecontext); @@ -302,7 +323,7 @@ class files_test extends core_reportbuilder_testcase { $this->setAdminUser(); $course = $this->getDataGenerator()->create_course(); - $coursecontext = context_course::instance($course->id); + $coursecontext = course::instance($course->id); $this->generate_test_files($coursecontext); @@ -326,17 +347,17 @@ class files_test extends core_reportbuilder_testcase { /** * Helper method to generate some test files (a user draft and course summary file) for reporting on * - * @param context_course $context + * @param course $context * @return int Draft item ID */ - protected function generate_test_files(context_course $context): int { + protected function generate_test_files(course $context): int { global $USER; $draftitemid = file_get_unused_draft_itemid(); // Populate user draft. get_file_storage()->create_file_from_string([ - 'contextid' => context_user::instance($USER->id)->id, + 'contextid' => user::instance($USER->id)->id, 'userid' => $USER->id, 'component' => 'user', 'filearea' => 'draft', diff --git a/lang/en/moodle.php b/lang/en/moodle.php index c4dceb445f9..afa41793668 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -301,6 +301,7 @@ $string['contentbank'] = 'Content bank'; $string['context'] = 'Context'; $string['contextlevel'] = 'Context level'; $string['contextname'] = 'Context name'; +$string['contextparent'] = 'Parent'; $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 index c9870150ee4..2e085109a66 100644 --- a/lib/classes/reportbuilder/local/entities/context.php +++ b/lib/classes/reportbuilder/local/entities/context.php @@ -20,7 +20,7 @@ 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\filters\{select, text}; use core_reportbuilder\local\report\{column, filter}; use html_writer; use lang_string; @@ -81,6 +81,8 @@ class context extends base { * @return column[] */ protected function get_all_columns(): array { + global $DB; + $contextalias = $this->get_table_alias('context'); // Name. @@ -145,6 +147,49 @@ class context extends base { return context_helper::get_level_name($level); }); + // Path. + $columns[] = (new column( + 'path', + new lang_string('path'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_field("{$contextalias}.path") + ->set_is_sortable(true); + + // Parent (note we select the parent path in SQL, so that aggregation/grouping is on the parent data itself). + $columns[] = (new column( + 'parent', + new lang_string('contextparent'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + // The "path" column looks like "/1/2/3", for context ID 3. In order to select/group by the parent context, we + // concatenate a trailing slash (to prevent partial matches, e.g. "/1/2/31"), then replace "/3/" with empty string. + ->add_field(" + REPLACE( + " . $DB->sql_concat("{$contextalias}.path", "'/'") . ", + " . $DB->sql_concat("'/'", $DB->sql_cast_to_char("{$contextalias}.id"), "'/'") . ", + '' + )", 'parent' + ) + // Sorting may not order alphabetically, but will at least group contexts together. + ->set_is_sortable(true) + ->add_callback(static function (?string $parent): string { + + // System level (no parent) or null. + if ($parent === '' || $parent === null) { + return ''; + } + + $contextids = explode('/', $parent); + $contextid = (int) array_pop($contextids); + + return context_helper::instance_by_id($contextid)->get_context_name(); + }); + return $columns; } @@ -173,6 +218,16 @@ class context extends base { }, $levels); }); + // Path. + $filters[] = (new filter( + text::class, + 'path', + new lang_string('path'), + $this->get_entity_name(), + "{$contextalias}.path" + )) + ->add_joins($this->get_joins()); + return $filters; } }