diff --git a/cohort/classes/reportbuilder/local/entities/cohort.php b/cohort/classes/reportbuilder/local/entities/cohort.php index d3e5b5517c5..5af641cf597 100644 --- a/cohort/classes/reportbuilder/local/entities/cohort.php +++ b/cohort/classes/reportbuilder/local/entities/cohort.php @@ -22,6 +22,7 @@ use context; use context_helper; use lang_string; use stdClass; +use theme_config; use core_reportbuilder\local\entities\base; use core_reportbuilder\local\filters\boolean_select; use core_reportbuilder\local\filters\date; @@ -245,7 +246,14 @@ class cohort extends base { ->add_joins($this->get_joins()) ->set_type(column::TYPE_TEXT) ->add_fields("{$tablealias}.theme") - ->set_is_sortable(true); + ->set_is_sortable(true) + ->add_callback(static function (?string $theme): string { + if ((string) $theme === '') { + return ''; + } + + return get_string('pluginname', "theme_{$theme}"); + }); return $columns; } @@ -328,6 +336,22 @@ class cohort extends base { )) ->add_joins($this->get_joins()); + // Theme filter. + $filters[] = (new filter( + select::class, + 'theme', + new lang_string('theme'), + $this->get_entity_name(), + "{$tablealias}.theme", + )) + ->set_options_callback(static function(): array { + return array_map( + fn(theme_config $theme) => $theme->get_theme_name(), + get_list_of_themes(), + ); + }) + ->add_joins($this->get_joins()); + // Visible filter. $filters[] = (new filter( boolean_select::class, diff --git a/cohort/tests/reportbuilder/datasource/cohorts_test.php b/cohort/tests/reportbuilder/datasource/cohorts_test.php index 413f6563127..41952b2bd13 100644 --- a/cohort/tests/reportbuilder/datasource/cohorts_test.php +++ b/cohort/tests/reportbuilder/datasource/cohorts_test.php @@ -92,6 +92,8 @@ class cohorts_test extends core_reportbuilder_testcase { $this->resetAfterTest(); $this->setAdminUser(); + set_config('allowcohortthemes', true); + /** @var core_customfield_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_customfield'); @@ -103,6 +105,7 @@ class cohorts_test extends core_reportbuilder_testcase { 'name' => 'Legends', 'idnumber' => 'C101', 'description' => 'Cohort for the legends', + 'theme' => 'boost', 'customfield_hi' => 'Hello', ]); @@ -131,7 +134,7 @@ class cohorts_test extends core_reportbuilder_testcase { $this->assertNotEmpty($timecreated); $this->assertNotEmpty($timemodified); $this->assertEquals('Created manually', $component); - $this->assertEmpty($theme); + $this->assertEquals('Boost', $theme); $this->assertEquals('Hello', $custom); $this->assertNotEmpty($timeadded); $this->assertEquals(fullname($user), $fullname); @@ -184,6 +187,14 @@ class cohorts_test extends core_reportbuilder_testcase { 'Filter description (no match)' => ['cohort:description', [ 'cohort:description_operator' => text::IS_EMPTY, ], false], + 'Filter theme' => ['cohort:theme', [ + 'cohort:theme_operator' => select::EQUAL_TO, + 'cohort:theme_value' => 'boost', + ], true], + 'Filter theme (no match)' => ['cohort:theme', [ + 'cohort:theme_operator' => select::EQUAL_TO, + 'cohort:theme_value' => 'classic', + ], false], 'Filter visible' => ['cohort:visible', [ 'cohort:visible_operator' => boolean_select::CHECKED, ], true], @@ -225,11 +236,14 @@ class cohorts_test extends core_reportbuilder_testcase { public function test_datasource_filters(string $filtername, array $filtervalues, bool $expectmatch): void { $this->resetAfterTest(); + set_config('allowcohortthemes', true); + // Test subject. $cohort = $this->getDataGenerator()->create_cohort([ 'name' => 'Legends', 'idnumber' => 'C101', 'description' => 'Cohort for the legends', + 'theme' => 'boost', ]); $user = $this->getDataGenerator()->create_user(['username' => 'lionel']); diff --git a/course/classes/category.php b/course/classes/category.php index 1a12b9b8c52..8a546a7ea62 100644 --- a/course/classes/category.php +++ b/course/classes/category.php @@ -629,10 +629,9 @@ class core_course_category implements renderable, cacheable_object, IteratorAggr } // Delete theme usage cache if the theme has been changed. - if (isset($data->theme)) { - $oldcategory = $DB->get_record('course_categories', ['id' => $data->id]); - if ($data->theme != $oldcategory->theme) { - theme_delete_used_in_context_cache($data->theme, (string)$oldcategory->theme); + if (isset($newcategory->theme)) { + if ($newcategory->theme != $this->theme) { + theme_delete_used_in_context_cache($newcategory->theme, (string) $this->theme); } } diff --git a/course/classes/reportbuilder/local/entities/course_category.php b/course/classes/reportbuilder/local/entities/course_category.php index 965baf83035..31f9586ad32 100644 --- a/course/classes/reportbuilder/local/entities/course_category.php +++ b/course/classes/reportbuilder/local/entities/course_category.php @@ -24,9 +24,10 @@ use html_writer; use lang_string; use moodle_url; use stdClass; +use theme_config; use core_course_category; use core_reportbuilder\local\entities\base; -use core_reportbuilder\local\filters\{category, text}; +use core_reportbuilder\local\filters\{category, select, text}; use core_reportbuilder\local\report\column; use core_reportbuilder\local\report\filter; @@ -199,6 +200,24 @@ class course_category extends base { return format_text($description, $category->descriptionformat, ['context' => $context->id]); }); + // Theme column. + $columns[] = (new column( + 'theme', + new lang_string('theme'), + $this->get_entity_name() + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TEXT) + ->add_fields("{$tablealias}.theme") + ->set_is_sortable(true) + ->add_callback(static function (?string $theme): string { + if ((string) $theme === '') { + return ''; + } + + return get_string('pluginname', "theme_{$theme}"); + }); + // Course count column. $columns[] = (new column( 'coursecount', @@ -254,6 +273,22 @@ class course_category extends base { )) ->add_joins($this->get_joins()); + // Theme filter. + $filters[] = (new filter( + select::class, + 'theme', + new lang_string('theme'), + $this->get_entity_name(), + "{$tablealias}.theme", + )) + ->set_options_callback(static function(): array { + return array_map( + fn(theme_config $theme) => $theme->get_theme_name(), + get_list_of_themes(), + ); + }) + ->add_joins($this->get_joins()); + return $filters; } diff --git a/course/tests/reportbuilder/datasource/categories_test.php b/course/tests/reportbuilder/datasource/categories_test.php index 34b6276cc31..5b2f9b78ecd 100644 --- a/course/tests/reportbuilder/datasource/categories_test.php +++ b/course/tests/reportbuilder/datasource/categories_test.php @@ -69,7 +69,14 @@ class categories_test extends core_reportbuilder_testcase { $this->resetAfterTest(); - $category = $this->getDataGenerator()->create_category(['name' => 'Zoo', 'idnumber' => 'Z01', 'description' => 'Animals']); + set_config('allowcategorythemes', true); + + $category = $this->getDataGenerator()->create_category([ + 'name' => 'Zoo', + 'idnumber' => 'Z01', + 'description' => 'Animals', + 'theme' => 'boost', + ]); $course = $this->getDataGenerator()->create_course(['category' => $category->id, 'fullname' => 'Zebra']); // Add a cohort. @@ -88,6 +95,7 @@ class categories_test extends core_reportbuilder_testcase { 'sortenabled' => 1]); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:path']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:description']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:theme']); // Add column from each of our entities. $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']); @@ -98,19 +106,23 @@ class categories_test extends core_reportbuilder_testcase { $content = $this->get_custom_report_content($report->get('id')); $this->assertCount(2, $content); - [$namewithlink, $path, $description, $coursename, $cohortname, $rolename, $userfullname] = array_values($content[0]); + [$namewithlink, $path, $description, $theme, $coursename, $cohortname, $rolename, $userfullname] = + array_values($content[0]); $this->assertStringContainsString(get_string('defaultcategoryname'), $namewithlink); $this->assertEquals(get_string('defaultcategoryname'), $path); $this->assertEmpty($description); + $this->assertEmpty($theme); $this->assertEmpty($coursename); $this->assertEmpty($cohortname); $this->assertEmpty($rolename); $this->assertEmpty($userfullname); - [$namewithlink, $path, $description, $coursename, $cohortname, $rolename, $userfullname] = array_values($content[1]); + [$namewithlink, $path, $description, $theme, $coursename, $cohortname, $rolename, $userfullname] = + array_values($content[1]); $this->assertStringContainsString($category->get_formatted_name(), $namewithlink); $this->assertEquals($category->get_nested_name(false), $path); $this->assertEquals(format_text($category->description, $category->descriptionformat), $description); + $this->assertEquals('Boost', $theme); $this->assertEquals($course->fullname, $coursename); $this->assertEquals($cohort->name, $cohortname); $this->assertEquals('Manager', $rolename); @@ -151,6 +163,14 @@ class categories_test extends core_reportbuilder_testcase { 'course_category:idnumber_operator' => text::IS_EQUAL_TO, 'course_category:idnumber_value' => 'P01', ], false], + 'Filter category theme' => ['course_category:theme', [ + 'course_category:theme_operator' => select::EQUAL_TO, + 'course_category:theme_value' => 'boost', + ], true], + 'Filter category theme (no match)' => ['course_category:theme', [ + 'course_category:theme_operator' => select::EQUAL_TO, + 'course_category:theme_value' => 'classic', + ], false], // Course. 'Filter course fullname' => ['course:fullname', [ @@ -208,8 +228,10 @@ class categories_test extends core_reportbuilder_testcase { $this->resetAfterTest(); + set_config('allowcategorythemes', true); + // Get the default category, modify it so we can filter each value. - ($category = core_course_category::get_default())->update(['name' => 'Zoo', 'idnumber' => 'Z01']); + ($category = core_course_category::get_default())->update(['name' => 'Zoo', 'idnumber' => 'Z01', 'theme' => 'boost']); $course = $this->getDataGenerator()->create_course(['category' => $category->id, 'fullname' => 'Zebra']); // Add a cohort. diff --git a/course/tests/reportbuilder/datasource/courses_test.php b/course/tests/reportbuilder/datasource/courses_test.php index c1669a0db5e..46353b23b96 100644 --- a/course/tests/reportbuilder/datasource/courses_test.php +++ b/course/tests/reportbuilder/datasource/courses_test.php @@ -91,6 +91,7 @@ class courses_test extends core_reportbuilder_testcase { 'category' => $category->id, 'fullname' => 'Cats', 'summary' => 'Course description', + 'theme' => 'boost', 'tags' => ['Horses'], ]); @@ -171,7 +172,7 @@ class courses_test extends core_reportbuilder_testcase { $this->assertEquals('No', $coursegroupmodeforce); $this->assertEmpty($courselang); $this->assertEmpty($coursecalendar); - $this->assertEmpty($coursetheme); + $this->assertEquals('Boost', $coursetheme); $this->assertEquals('No', $coursecompletion); $this->assertEmpty($coursedownload); $this->assertEquals(userdate($course->timecreated), $coursetimecreated); diff --git a/reportbuilder/classes/local/entities/course.php b/reportbuilder/classes/local/entities/course.php index 34382c45d82..7d4586c045e 100644 --- a/reportbuilder/classes/local/entities/course.php +++ b/reportbuilder/classes/local/entities/course.php @@ -32,6 +32,7 @@ use core_reportbuilder\local\report\filter; use html_writer; use lang_string; use stdClass; +use theme_config; defined('MOODLE_INTERNAL') || die(); @@ -140,7 +141,7 @@ class course extends base { 'groupmodeforce' => new lang_string('groupmodeforce', 'group'), 'lang' => new lang_string('forcelanguage'), 'calendartype' => new lang_string('forcecalendartype', 'calendar'), - 'theme' => new lang_string('forcetheme'), + 'theme' => new lang_string('theme'), 'enablecompletion' => new lang_string('enablecompletion', 'completion'), 'downloadcontent' => new lang_string('downloadcoursecontent', 'course'), 'timecreated' => new lang_string('timecreated', 'core_reportbuilder'), @@ -411,16 +412,10 @@ class course extends base { * @return array */ public static function get_options_for_theme(): array { - $options = []; - - $themeobjects = get_list_of_themes(); - foreach ($themeobjects as $key => $theme) { - if (empty($theme->hidefromselector)) { - $options[$key] = get_string('pluginname', "theme_{$theme->name}"); - } - } - - return $options; + return array_map( + fn(theme_config $theme) => $theme->get_theme_name(), + get_list_of_themes(), + ); } /** @@ -454,15 +449,16 @@ class course extends base { 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 ($this->get_course_field_type($fieldname) === column::TYPE_BOOLEAN) { - return format::boolean_as_text($value); - } - if (in_array($fieldname, ['fullname', 'shortname'])) { if (!$row->courseid) { return ''; diff --git a/reportbuilder/classes/local/entities/user.php b/reportbuilder/classes/local/entities/user.php index ef1ddc5b6d0..4893e9b57e2 100644 --- a/reportbuilder/classes/local/entities/user.php +++ b/reportbuilder/classes/local/entities/user.php @@ -27,6 +27,7 @@ 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; @@ -311,14 +312,8 @@ class user extends base { ->set_is_sortable($this->is_sortable($userfield)) ->add_callback([$this, 'format'], $userfield); - // Some columns also have specific format callbacks. - if ($userfield === 'country') { - $column->add_callback(static function(string $country): string { - $countries = get_string_manager()->get_list_of_countries(true); - return $countries[$country] ?? ''; - }); - } else if ($userfield === 'description') { - // Select enough fields in order to format the column. + // 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 . " @@ -368,6 +363,12 @@ class user extends base { 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 ''; @@ -428,6 +429,7 @@ class user extends base { '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'), @@ -577,12 +579,43 @@ class user extends base { 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 country. * * @return string[] */ public static function get_options_for_country(): array { - return array_map('shorten_text', get_string_manager()->get_list_of_countries()); + return get_string_manager()->get_list_of_countries(); + } + + /** + * 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(), + ); } } diff --git a/reportbuilder/tests/datasource_test.php b/reportbuilder/tests/datasource_test.php index 4128fa5ca82..66a01fcad2e 100644 --- a/reportbuilder/tests/datasource_test.php +++ b/reportbuilder/tests/datasource_test.php @@ -55,7 +55,7 @@ class datasource_test extends advanced_testcase { 'All column' => [ [], [], - 29, + 30, ], 'Include columns (picture, fullname, fullnamewithlink, fullnamewithpicture, fullnamewithpicturelink)' => [ ['picture', 'fullname*'], @@ -65,7 +65,7 @@ class datasource_test extends advanced_testcase { 'Exclude columns (picture, fullname, fullnamewithlink, fullnamewithpicture, fullnamewithpicturelink)' => [ [], ['picture', 'fullname*'], - 24, + 25, ], ]; } @@ -112,7 +112,7 @@ class datasource_test extends advanced_testcase { 'All filters' => [ [], [], - 27, + 28, ], 'Include filters (department, phone1, phone2)' => [ ['department', 'phone*'], @@ -122,7 +122,7 @@ class datasource_test extends advanced_testcase { 'Exclude filters (department, phone1, phone2)' => [ [], ['department', 'phone*'], - 24, + 25, ], ]; } @@ -169,7 +169,7 @@ class datasource_test extends advanced_testcase { 'All conditions' => [ [], [], - 27, + 28, ], 'Include conditions (department, phone1, phone2)' => [ ['department', 'phone*'], @@ -179,7 +179,7 @@ class datasource_test extends advanced_testcase { 'Exclude conditions (department, phone1, phone2)' => [ [], ['department', 'phone*'], - 24, + 25, ], ]; } diff --git a/user/tests/reportbuilder/datasource/users_test.php b/user/tests/reportbuilder/datasource/users_test.php index b201f4bc497..5364a03ef6e 100644 --- a/user/tests/reportbuilder/datasource/users_test.php +++ b/user/tests/reportbuilder/datasource/users_test.php @@ -77,6 +77,7 @@ class users_test extends core_reportbuilder_testcase { 'idnumber' => 'U0001', 'city' => 'London', 'country' => 'GB', + 'theme' => 'boost', 'interests' => ['Horses'], ]); @@ -114,6 +115,7 @@ class users_test extends core_reportbuilder_testcase { $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:moodlenetprofile']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:timecreated']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastip']); + $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:theme']); // Tags. $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:name']); @@ -159,9 +161,10 @@ class users_test extends core_reportbuilder_testcase { $this->assertEquals($user->moodlenetprofile, $userrow[22]); $this->assertNotEmpty($userrow[23]); $this->assertEquals('0.0.0.0', $userrow[24]); - $this->assertEquals('Horses', $userrow[25]); - $this->assertStringContainsString('Horses', $userrow[26]); - $this->assertEquals($cohort->name, $userrow[27]); + $this->assertEquals('Boost', $userrow[25]); + $this->assertEquals('Horses', $userrow[26]); + $this->assertStringContainsString('Horses', $userrow[27]); + $this->assertEquals($cohort->name, $userrow[28]); } /** @@ -280,6 +283,14 @@ class users_test extends core_reportbuilder_testcase { 'user:country_operator' => select::EQUAL_TO, 'user:country_value' => 'AU', ], false], + 'Filter theme' => ['user:theme', [ + 'user:theme_operator' => select::EQUAL_TO, + 'user:theme_value' => 'boost', + ], true], + 'Filter theme (no match)' => ['user:theme', [ + 'user:theme_operator' => select::EQUAL_TO, + 'user:theme_value' => 'classic', + ], false], 'Filter description' => ['user:description', [ 'user:description_operator' => text::CONTAINS, 'user:description_value' => 'Hello there', @@ -424,6 +435,7 @@ class users_test extends core_reportbuilder_testcase { 'address' => 'Big Farm', 'city' => 'Barcelona', 'country' => 'ES', + 'theme' => 'boost', 'description' => 'Hello there', 'moodlenetprofile' => '@zoe1@example.com', 'interests' => ['Horses'],