Skip to content

Commit

Permalink
MDL-78427 core_theme: Add theme usage report and icon
Browse files Browse the repository at this point in the history
In addition to adding in theme usage reports, there is also the
addition of an icon on the theme cards which takes you to the report.
This icon only appears for that theme if it has been used in any
overriding context.
  • Loading branch information
davewoloszyn committed Feb 2, 2024
1 parent f30110b commit 40d397a
Show file tree
Hide file tree
Showing 24 changed files with 1,157 additions and 10 deletions.
29 changes: 21 additions & 8 deletions admin/settings/appearance.php
Expand Up @@ -293,14 +293,27 @@
new lang_string('configthemedesignermode', 'admin'), 0);
$setting->set_updatedcallback('theme_reset_all_caches');
$temp->add($setting);
$temp->add(new admin_setting_configcheckbox('allowuserthemes', new lang_string('allowuserthemes', 'admin'),
new lang_string('configallowuserthemes', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowcoursethemes', new lang_string('allowcoursethemes', 'admin'),
new lang_string('configallowcoursethemes', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowcategorythemes', new lang_string('allowcategorythemes', 'admin'),
new lang_string('configallowcategorythemes', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowcohortthemes', new lang_string('allowcohortthemes', 'admin'),
new lang_string('configallowcohortthemes', 'admin'), 0));

$setting = new admin_setting_configcheckbox('allowuserthemes', new lang_string('allowuserthemes', 'admin'),
new lang_string('configallowuserthemes', 'admin'), 0);
$setting->set_updatedcallback('theme_purge_used_in_context_caches');
$temp->add($setting);

$setting = new admin_setting_configcheckbox('allowcoursethemes', new lang_string('allowcoursethemes', 'admin'),
new lang_string('configallowcoursethemes', 'admin'), 0);
$setting->set_updatedcallback('theme_purge_used_in_context_caches');
$temp->add($setting);

$setting = new admin_setting_configcheckbox('allowcategorythemes', new lang_string('allowcategorythemes', 'admin'),
new lang_string('configallowcategorythemes', 'admin'), 0);
$setting->set_updatedcallback('theme_purge_used_in_context_caches');
$temp->add($setting);

$setting = new admin_setting_configcheckbox('allowcohortthemes', new lang_string('allowcohortthemes', 'admin'),
new lang_string('configallowcohortthemes', 'admin'), 0);
$setting->set_updatedcallback('theme_purge_used_in_context_caches');
$temp->add($setting);

$temp->add(new admin_setting_configcheckbox('allowthemechangeonurl', new lang_string('allowthemechangeonurl', 'admin'),
new lang_string('configallowthemechangeonurl', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('allowuserblockhiding', new lang_string('allowuserblockhiding', 'admin'),
Expand Down
13 changes: 12 additions & 1 deletion admin/templates/themeselector/theme_card.mustache
Expand Up @@ -27,7 +27,8 @@
"current": true,
"actionurl": "http://moodlesite/admin/themeselector.php",
"sesskey": "123XYZ",
"settingsurl": "http://moodlesite/admin/settings.php?section=themesettingboost"
"settingsurl": "http://moodlesite/admin/settings.php?section=themesettingboost",
"reporturl": "http://moodlesite/report/themeusage/index.php?themechoice=boost"
}
}}
<div class="card dashboard-card" role="listitem" id="theme-card-{{choose}}" aria-labelledby="theme-name-{{choose}} {{#current}}current-theme-{{choose}}{{/current}}">
Expand All @@ -53,6 +54,16 @@
<i class="icon fa fa-info-circle m-0" aria-hidden="true"></i>
<span class="sr-only">{{#str}}previewthemename, moodle, {{name}}{{/str}}</span>
</button>
{{#reporturl}}
<a
href="{{reporturl}}"
id="theme-usage-report-{{choose}}"
class="btn btn-link p-0 ml-2"
title="{{#str}}themeusagereportname, admin, {{name}}{{/str}}">
<i class="icon fa fa-area-chart m-0" aria-hidden="true"></i>
<span class="sr-only">{{#str}}themeusagereportname, admin, {{name}}{{/str}}</span>
</a>
{{/reporturl}}
{{#settingsurl}}
<a
href="{{settingsurl}}"
Expand Down
7 changes: 7 additions & 0 deletions admin/themeselector.php
Expand Up @@ -127,6 +127,13 @@
$themedata['settingsurl'] = $settingsurl;
}

// Link to the theme usage report if override enabled and it is being used in at least one context.
if (\core\output\theme_usage::is_theme_used_in_any_context($themename) === \core\output\theme_usage::THEME_IS_USED) {
$reporturl = new moodle_url($CFG->wwwroot . '/report/themeusage/index.php');
$reporturl->params(['themechoice' => $themename]);
$themedata['reporturl'] = $reporturl->out(false);
}

$data[$index] = $themedata;
$index++;
}
Expand Down
9 changes: 9 additions & 0 deletions cohort/lib.php
Expand Up @@ -101,6 +101,15 @@ function cohort_update_cohort($cohort) {
if (empty($CFG->allowcohortthemes) && isset($cohort->theme)) {
unset($cohort->theme);
}

// Delete theme usage cache if the theme has been changed.
if (isset($cohort->theme)) {
$oldcohort = $DB->get_record('cohort', ['id' => $cohort->id]);
if ($cohort->theme != $oldcohort->theme) {
theme_delete_used_in_context_cache($cohort->theme, $oldcohort->theme);
}
}

$cohort->timemodified = time();

// Update custom fields if there are any of them in the form.
Expand Down
8 changes: 8 additions & 0 deletions course/classes/category.php
Expand Up @@ -628,6 +628,14 @@ public function update($data, $editoroptions = null) {
fix_course_sortorder();
}

// 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);
}
}

$newcategory->timemodified = time();

$categorycontext = $this->get_context();
Expand Down
1 change: 0 additions & 1 deletion course/edit_form.php
Expand Up @@ -488,7 +488,6 @@ function definition_after_data() {
// Tweak the form with values provided by custom fields in use.
$handler = core_course\customfield\course_handler::create();
$handler->instance_form_definition_after_data($mform, empty($courseid) ? 0 : $courseid);

}

/**
Expand Down
5 changes: 5 additions & 0 deletions course/lib.php
Expand Up @@ -2485,6 +2485,11 @@ function update_course($data, $editoroptions = NULL) {
$DB->delete_records('course_format_options',
array('courseid' => $course->id, 'format' => $oldcourse->format));
}

// Delete theme usage cache if the theme has been changed.
if (isset($data->theme) && ($data->theme != $oldcourse->theme)) {
theme_delete_used_in_context_cache($data->theme, $oldcourse->theme);
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions lang/en/admin.php
Expand Up @@ -1463,6 +1463,8 @@
$string['themeselector'] = 'Themes';
$string['themesettingsadvanced'] = 'Advanced theme settings';
$string['themeeditsettingsname'] = 'Edit theme settings \'{$a}\'';
$string['themesettingsname'] = 'Theme settings \'{$a}\'';
$string['themeusagereportname'] = 'Theme usage report \'{$a}\'';
$string['therewereerrors'] = 'There were errors in your data';
$string['thirdpartylibrary'] = 'Library';
$string['thirdpartylibrarylocation'] = 'Location';
Expand Down
1 change: 1 addition & 0 deletions lang/en/cache.php
Expand Up @@ -96,6 +96,7 @@
$string['cachedef_string'] = 'Language string cache';
$string['cachedef_tags'] = 'Tags collections and areas';
$string['cachedef_temp_tables'] = 'Temporary tables cache';
$string['cachedef_theme_usedincontext'] = 'A theme has been used in context to override the default theme';
$string['cachedef_userselections'] = 'Data used to persist user selections throughout Moodle';
$string['cachedef_user_favourite_course_content_items'] = 'User\'s starred items';
$string['cachedef_user_group_groupings'] = 'User\'s groupings and groups per course';
Expand Down
127 changes: 127 additions & 0 deletions lib/classes/output/theme_usage.php
@@ -0,0 +1,127 @@
<?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/>.

namespace core\output;

/**
* This class houses methods for checking theme usage in a given context.
*
* @package core
* @category output
* @copyright 2024 David Woloszyn <david.woloszyn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class theme_usage {

/** @var string The theme usage type for users. */
public const THEME_USAGE_TYPE_USER = 'user';

/** @var string The theme usage type for courses. */
public const THEME_USAGE_TYPE_COURSE = 'course';

/** @var string The theme usage type for cohorts. */
public const THEME_USAGE_TYPE_COHORT = 'cohort';

/** @var string The theme usage type for categories. */
public const THEME_USAGE_TYPE_CATEGORY = 'category';

/** @var string The theme usage type for all. */
public const THEME_USAGE_TYPE_ALL = 'all';

/** @var int The theme is used in context. */
public const THEME_IS_USED = 1;

/** @var int The theme is not used in context. */
public const THEME_IS_NOT_USED = 0;

/**
* Check if the theme is used in any context (e.g. user, course, cohort, category).
*
* This query is cached.
*
* @param string $themename The theme to check.
* @return int Return 1 if at least one record was found, 0 if none.
*/
public static function is_theme_used_in_any_context(string $themename): int {
global $DB;
$cache = \cache::make('core', 'theme_usedincontext');
$isused = $cache->get($themename);

if ($isused === false) {

$sqlunions = [];

// For each context, check if the config is enabled and there is at least one use.
if (get_config('core', 'allowuserthemes')) {
$sqlunions[self::THEME_USAGE_TYPE_USER] = "
SELECT u.id
FROM {user} u
WHERE u.theme = :usertheme
";
}

if (get_config('core', 'allowcoursethemes')) {
$sqlunions[self::THEME_USAGE_TYPE_COURSE] = "
SELECT c.id
FROM {course} c
WHERE c.theme = :coursetheme
";
}

if (get_config('core', 'allowcohortthemes')) {
$sqlunions[self::THEME_USAGE_TYPE_COHORT] = "
SELECT co.id
FROM {cohort} co
WHERE co.theme = :cohorttheme
";
}

if (get_config('core', 'allowcategorythemes')) {
$sqlunions[self::THEME_USAGE_TYPE_CATEGORY] = "
SELECT cat.id
FROM {course_categories} cat
WHERE cat.theme = :categorytheme
";
}

// Union the sql statements from the different tables.
if (!empty($sqlunions)) {
$sql = implode(' UNION ', $sqlunions);

// Prepare params.
$params = [];
foreach ($sqlunions as $type => $val) {
$params[$type . 'theme'] = $themename;
}

$result = $DB->record_exists_sql($sql, $params);
}

if (!empty($result)) {
$isused = self::THEME_IS_USED;
} else {
$isused = self::THEME_IS_NOT_USED;
}

// Cache the result so we don't have to keep checking for this theme.
$cache->set($themename, $isused);
return $isused;

} else {
return $isused;
}
}
}
9 changes: 9 additions & 0 deletions lib/db/caches.php
Expand Up @@ -609,4 +609,13 @@
'changesincourse',
],
],

// A theme has been used in context to override the default theme.
// Applies to user, cohort, category and course.
'theme_usedincontext' => [
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true,
'simpledata' => true,
'staticacceleration' => true,
],
);
27 changes: 27 additions & 0 deletions lib/outputlib.php
Expand Up @@ -300,6 +300,33 @@ function theme_set_designer_mod($state) {
theme_reset_all_caches();
}

/**
* Purge theme used in context caches.
*/
function theme_purge_used_in_context_caches() {
\cache::make('core', 'theme_usedincontext')->purge();
}

/**
* Delete theme used in context cache for a particular theme.
*
* When switching themes, both old and new theme caches are deleted.
* This gives the query the opportunity to recache accurate results for both themes.
*
* @param string $newtheme The incoming new theme.
* @param string $oldtheme The theme that was already set.
*/
function theme_delete_used_in_context_cache(string $newtheme, string $oldtheme): void {
if ((strlen($newtheme) > 0) && (strlen($oldtheme) > 0)) {
// Theme -> theme.
\cache::make('core', 'theme_usedincontext')->delete($oldtheme);
\cache::make('core', 'theme_usedincontext')->delete($newtheme);
} else {
// No theme -> theme, or theme -> no theme.
\cache::make('core', 'theme_usedincontext')->delete($newtheme . $oldtheme);
}
}

/**
* This class represents the configuration variables of a Moodle theme.
*
Expand Down

0 comments on commit 40d397a

Please sign in to comment.