diff --git a/admin/amd/build/themeselector/preview_modal.min.js b/admin/amd/build/themeselector/preview_modal.min.js new file mode 100644 index 00000000000..370331cb848 --- /dev/null +++ b/admin/amd/build/themeselector/preview_modal.min.js @@ -0,0 +1,10 @@ +define("core_admin/themeselector/preview_modal",["exports","core/modal_events","core/modal_cancel","core/modal_save_cancel","core/notification","core/templates","core/str"],(function(_exports,_modal_events,_modal_cancel,_modal_save_cancel,_notification,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Modal for theme previews. + * + * @module core_admin/themeselector/preview_modal + * @copyright 2023 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal_events=_interopRequireDefault(_modal_events),_modal_cancel=_interopRequireDefault(_modal_cancel),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates);const SELECTORS_PREVIEW='[data-action="preview"]';_exports.init=()=>{registerListenerEvents()};const registerListenerEvents=()=>{document.addEventListener("click",(e=>{const preview=e.target.closest(SELECTORS_PREVIEW);preview&&buildModal(preview).catch(_notification.default.exception)}))},buildModal=async element=>{let description=await(0,_str.getString)("choosereadme","theme_"+element.getAttribute("data-choose"));const data={name:element.getAttribute("data-name"),image:element.getAttribute("data-image"),description:description.replace(/<[^>]+>/g," "),current:element.getAttribute("data-current"),actionurl:element.getAttribute("data-actionurl"),choose:element.getAttribute("data-choose"),sesskey:element.getAttribute("data-sesskey")};let modalTemplate=_modal_save_cancel.default;data.current&&(modalTemplate=_modal_cancel.default);const modal=await modalTemplate.create({title:data.name,body:_templates.default.render("core_admin/themeselector/theme_preview_modal",data),large:!0,buttons:{save:(0,_str.getString)("selecttheme","moodle"),cancel:(0,_str.getString)("closebuttontitle","moodle")},show:!0});modal.getRoot().on(_modal_events.default.save,(()=>{modal.getRoot().find("form").submit()}))}})); + +//# sourceMappingURL=preview_modal.min.js.map \ No newline at end of file diff --git a/admin/amd/build/themeselector/preview_modal.min.js.map b/admin/amd/build/themeselector/preview_modal.min.js.map new file mode 100644 index 00000000000..2e19cc298ff --- /dev/null +++ b/admin/amd/build/themeselector/preview_modal.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"preview_modal.min.js","sources":["../../src/themeselector/preview_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Modal for theme previews.\n *\n * @module core_admin/themeselector/preview_modal\n * @copyright 2023 David Woloszyn \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalEvents from 'core/modal_events';\nimport ModalCancel from 'core/modal_cancel';\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport Notification from 'core/notification';\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\n\nconst SELECTORS = {\n PREVIEW: '[data-action=\"preview\"]',\n};\n\n/**\n * Entrypoint of the js.\n *\n * @method init\n */\nexport const init = () => {\n registerListenerEvents();\n};\n\n/**\n * Register theme related event listeners.\n *\n * @method registerListenerEvents\n */\nconst registerListenerEvents = () => {\n document.addEventListener('click', (e) => {\n const preview = e.target.closest(SELECTORS.PREVIEW);\n if (preview) {\n buildModal(preview).catch(Notification.exception);\n }\n });\n};\n\n/**\n * Build the modal with the provided data.\n *\n * @method buildModal\n * @param {object} element\n */\nconst buildModal = async(element) => {\n\n // This string can be long. We will fetch it with JS as opposed to passing it as an attribute.\n let description = await getString('choosereadme', 'theme_' + element.getAttribute('data-choose'));\n\n // Prepare data for modal.\n const data = {\n name: element.getAttribute('data-name'),\n image: element.getAttribute('data-image'),\n description: description.replace(/<[^>]+>/g, ' '), // Strip out HTML tags.\n current: element.getAttribute('data-current'),\n actionurl: element.getAttribute('data-actionurl'),\n choose: element.getAttribute('data-choose'),\n sesskey: element.getAttribute('data-sesskey'),\n };\n\n // Determine which modal template we should use.\n let modalTemplate = ModalSaveCancel;\n if (data.current) {\n modalTemplate = ModalCancel;\n }\n\n const modal = await modalTemplate.create({\n title: data.name,\n body: Templates.render('core_admin/themeselector/theme_preview_modal', data),\n large: true,\n buttons: {\n 'save': getString('selecttheme', 'moodle'),\n 'cancel': getString('closebuttontitle', 'moodle'),\n },\n show: true,\n });\n\n modal.getRoot().on(ModalEvents.save, () => {\n modal.getRoot().find('form').submit();\n });\n};\n"],"names":["SELECTORS","registerListenerEvents","document","addEventListener","e","preview","target","closest","buildModal","catch","Notification","exception","async","description","element","getAttribute","data","name","image","replace","current","actionurl","choose","sesskey","modalTemplate","ModalSaveCancel","ModalCancel","modal","create","title","body","Templates","render","large","buttons","show","getRoot","on","ModalEvents","save","find","submit"],"mappings":";;;;;;;gWA8BMA,kBACO,wCAQO,KAChBC,gCAQEA,uBAAyB,KAC3BC,SAASC,iBAAiB,SAAUC,UAC1BC,QAAUD,EAAEE,OAAOC,QAAQP,mBAC7BK,SACAG,WAAWH,SAASI,MAAMC,sBAAaC,eAW7CH,WAAaI,MAAAA,cAGXC,kBAAoB,kBAAU,eAAgB,SAAWC,QAAQC,aAAa,sBAG5EC,KAAO,CACTC,KAAMH,QAAQC,aAAa,aAC3BG,MAAOJ,QAAQC,aAAa,cAC5BF,YAAaA,YAAYM,QAAQ,WAAY,KAC7CC,QAASN,QAAQC,aAAa,gBAC9BM,UAAWP,QAAQC,aAAa,kBAChCO,OAAQR,QAAQC,aAAa,eAC7BQ,QAAST,QAAQC,aAAa,qBAI9BS,cAAgBC,2BAChBT,KAAKI,UACLI,cAAgBE,6BAGdC,YAAcH,cAAcI,OAAO,CACrCC,MAAOb,KAAKC,KACZa,KAAMC,mBAAUC,OAAO,+CAAgDhB,MACvEiB,OAAO,EACPC,QAAS,OACG,kBAAU,cAAe,kBACvB,kBAAU,mBAAoB,WAE5CC,MAAM,IAGVR,MAAMS,UAAUC,GAAGC,sBAAYC,MAAM,KACjCZ,MAAMS,UAAUI,KAAK,QAAQC"} \ No newline at end of file diff --git a/admin/amd/src/themeselector/preview_modal.js b/admin/amd/src/themeselector/preview_modal.js new file mode 100644 index 00000000000..9ae089be841 --- /dev/null +++ b/admin/amd/src/themeselector/preview_modal.js @@ -0,0 +1,100 @@ +// 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 . + +/** + * Modal for theme previews. + * + * @module core_admin/themeselector/preview_modal + * @copyright 2023 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import ModalEvents from 'core/modal_events'; +import ModalCancel from 'core/modal_cancel'; +import ModalSaveCancel from 'core/modal_save_cancel'; +import Notification from 'core/notification'; +import Templates from 'core/templates'; +import {getString} from 'core/str'; + +const SELECTORS = { + PREVIEW: '[data-action="preview"]', +}; + +/** + * Entrypoint of the js. + * + * @method init + */ +export const init = () => { + registerListenerEvents(); +}; + +/** + * Register theme related event listeners. + * + * @method registerListenerEvents + */ +const registerListenerEvents = () => { + document.addEventListener('click', (e) => { + const preview = e.target.closest(SELECTORS.PREVIEW); + if (preview) { + buildModal(preview).catch(Notification.exception); + } + }); +}; + +/** + * Build the modal with the provided data. + * + * @method buildModal + * @param {object} element + */ +const buildModal = async(element) => { + + // This string can be long. We will fetch it with JS as opposed to passing it as an attribute. + let description = await getString('choosereadme', 'theme_' + element.getAttribute('data-choose')); + + // Prepare data for modal. + const data = { + name: element.getAttribute('data-name'), + image: element.getAttribute('data-image'), + description: description.replace(/<[^>]+>/g, ' '), // Strip out HTML tags. + current: element.getAttribute('data-current'), + actionurl: element.getAttribute('data-actionurl'), + choose: element.getAttribute('data-choose'), + sesskey: element.getAttribute('data-sesskey'), + }; + + // Determine which modal template we should use. + let modalTemplate = ModalSaveCancel; + if (data.current) { + modalTemplate = ModalCancel; + } + + const modal = await modalTemplate.create({ + title: data.name, + body: Templates.render('core_admin/themeselector/theme_preview_modal', data), + large: true, + buttons: { + 'save': getString('selecttheme', 'moodle'), + 'cancel': getString('closebuttontitle', 'moodle'), + }, + show: true, + }); + + modal.getRoot().on(ModalEvents.save, () => { + modal.getRoot().find('form').submit(); + }); +}; diff --git a/admin/classes/output/theme_selector.php b/admin/classes/output/theme_selector.php new file mode 100644 index 00000000000..645e0fe497f --- /dev/null +++ b/admin/classes/output/theme_selector.php @@ -0,0 +1,65 @@ +. + +namespace core_admin\output; + +use moodle_url; +use renderable; +use renderer_base; +use stdClass; +use templatable; + +/** + * Theme selector renderable. + * + * @package core_admin + * @copyright 2023 David Woloszyn + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class theme_selector implements renderable, templatable { + + /** @var array $themedata Theme data to pass to the template. */ + private $themedata = null; + + /** + * Constructor. + * + * @param array $themedata Theme data used for template. + */ + public function __construct(array $themedata) { + $this->themedata = $themedata; + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param renderer_base $output Renderer base. + * @return stdClass + */ + public function export_for_template(renderer_base $output): stdClass { + + $data = new stdClass(); + // Theme data used to populate cards and modal. + $data->themes = $this->themedata; + // Reset theme caches button. + $reseturl = new moodle_url('admin/themeselector.php', ['sesskey' => sesskey(), 'reset' => 1]); + $resetbutton = new \single_button($reseturl, get_string('themeresetcaches', 'admin'), 'post', + \single_button::BUTTON_SECONDARY); + $data->resetbutton = $resetbutton->export_for_template($output); + + return $data; + } +} diff --git a/admin/renderer.php b/admin/renderer.php index 6141dfec8a6..7f943869ca3 100644 --- a/admin/renderer.php +++ b/admin/renderer.php @@ -2253,4 +2253,15 @@ class core_admin_renderer extends plugin_renderer_base { return $this->warning($xmlrpcwarning); } + + /** + * Renders the theme selector list. + * + * @param core_admin\output\theme_selector $themeselector + * @return string HTML + */ + public function theme_selector_list(core_admin\output\theme_selector $themeselector): string { + $renderable = $themeselector->export_for_template($this); + return $this->render_from_template('core_admin/themeselector/theme_selector', $renderable); + } } diff --git a/admin/settings/appearance.php b/admin/settings/appearance.php index 26f3096ade6..3c583443235 100644 --- a/admin/settings/appearance.php +++ b/admin/settings/appearance.php @@ -47,7 +47,8 @@ reports,core_reportbuilder|/reportbuilder/index.php', '10' )); $ADMIN->add('themes', $temp); - $ADMIN->add('themes', new admin_externalpage('themeselector', new lang_string('themeselector','admin'), $CFG->wwwroot . '/theme/index.php')); + $ADMIN->add('themes', new admin_externalpage('themeselector', + new lang_string('themeselector', 'admin'), $CFG->wwwroot . '/admin/themeselector.php')); // settings for each theme foreach (core_component::get_plugin_list('theme') as $theme => $themedir) { diff --git a/admin/templates/themeselector/theme_card.mustache b/admin/templates/themeselector/theme_card.mustache new file mode 100644 index 00000000000..9cb3bb6fea7 --- /dev/null +++ b/admin/templates/themeselector/theme_card.mustache @@ -0,0 +1,76 @@ +{{! + 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 . +}} +{{! + @template core_admin/themeselector/theme_card + + This template renders the singular card for a theme. + + Example context (json): + { + "name": "Boost", + "choose": "boost", + "image": "http://moodlesite/theme/image.php?theme=boost&image=screenshot&component=theme", + "current": true, + "actionurl": "http://moodlesite/admin/themeselector.php", + "sesskey": "123XYZ" + } +}} +
+
+
+
+
+

{{name}}

+
+
+ +
+
+
+
+ {{#current}} + {{#str}}currenttheme, moodle{{/str}} + {{/current}} + {{^current}} + {{#actionurl}} +
+ + + +
+ {{/actionurl}} + {{/current}} +
+
+ diff --git a/admin/templates/themeselector/theme_preview_modal.mustache b/admin/templates/themeselector/theme_preview_modal.mustache new file mode 100644 index 00000000000..605f6762906 --- /dev/null +++ b/admin/templates/themeselector/theme_preview_modal.mustache @@ -0,0 +1,55 @@ +{{! + 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 . +}} +{{! + @template core_admin/themeselector/theme_preview_modal + + This template renders the modal for the selected theme. + + Example context (json): + { + "name": "Boost", + "choose": "boost", + "image": "http://moodlesite/theme/image.php?theme=boost&image=screenshot&component=theme", + "description": "Boost is a modern highly-customisable theme...", + "current": true, + "actionurl": "http://moodlesite/admin/themeselector.php", + "sesskey": "123XYZ" + } +}} +
+
+ +
+
+
+

{{#str}}themepreviewdescription, moodle, {{name}}{{/str}}

+
+
+ {{#current}} + {{#str}}currenttheme, moodle{{/str}} + {{/current}} +
+
+

{{description}}

+ {{#actionurl}} + + {{/actionurl}} +
diff --git a/admin/templates/themeselector/theme_selector.mustache b/admin/templates/themeselector/theme_selector.mustache new file mode 100644 index 00000000000..ffefda6cd45 --- /dev/null +++ b/admin/templates/themeselector/theme_selector.mustache @@ -0,0 +1,75 @@ +{{! + 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 . +}} +{{! + @template core_admin/themeselector/theme_selector + + This template renders the cards view for choosing a theme. + + Example context (json): + { + "themes": [ + { + "name": "Boost", + "choose": "boost", + "image": "http://moodlesite/theme/image.php?theme=boost&image=screenshot&component=theme", + "current": true, + "actionurl": "http://moodlesite/admin/themeselector.php", + "sesskey": "123XYZ" + }, + { + "name": "Classic", + "choose": "classic", + "image": "http://moodlesite/theme/image.php?theme=classic&image=screenshot&component=theme", + "actionurl": "http://moodlesite/admin/themeselector.php", + "sesskey": "123XYZ" + } + ], + "resetbutton": { + "id": "single_button123", + "method": "post", + "url": "index.php", + "label": "Clear theme caches", + "params": [ + { + "name": "sesskey", + "value": "123XYZ" + }, + { + "name": "reset", + "value": "1" + } + ] + } + } +}} +

{{#str}}themeselector, admin{{/str}}

+{{#resetbutton}} +
+ {{>core/single_button}} +
+{{/resetbutton}} +
+ {{#themes}} + {{>core_admin/themeselector/theme_card}} + {{/themes}} +
+ +{{#js}} +require(['core_admin/themeselector/preview_modal'], function(Modal) { + Modal.init(); +}); +{{/js}} diff --git a/admin/themeselector.php b/admin/themeselector.php new file mode 100644 index 00000000000..d83959b0d75 --- /dev/null +++ b/admin/themeselector.php @@ -0,0 +1,132 @@ +. + +/** + * Theme selector page for admin use. + * + * @package core_admin + * @copyright 2023 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../config.php'); +require_once($CFG->libdir . '/adminlib.php'); + +$choose = optional_param('choose', '', PARAM_PLUGIN); +$reset = optional_param('reset', 0, PARAM_BOOL); +$confirmation = optional_param('confirmation', 0, PARAM_BOOL); + +admin_externalpage_setup('themeselector'); + +unset($SESSION->theme); + +$PAGE->set_primary_active_tab('siteadminnode'); +$PAGE->navbar->add(get_string('themeselector', 'admin'), $PAGE->url); +$PAGE->set_pagelayout('standard'); + +// Clear theme cache. +if ($reset && confirm_sesskey()) { + theme_reset_all_caches(); +} + +// Change theme. +if (!empty($choose) && confirm_sesskey()) { + + // Load the theme to make sure it is valid. + $theme = theme_config::load($choose); + + if ($theme instanceof \theme_config) { + set_config('theme', $theme->name); + $notifytype = 'success'; + $notifymessage = get_string('themesaved'); + } else { + $notifytype = 'error'; + $notifymessage = get_string('error'); + } + + // Redirect with notification. + redirect(new moodle_url('/admin/themeselector.php'), $notifymessage, null, $notifytype); +} + +// Insert header. +echo $OUTPUT->header(); + +// Prepare data for rendering. +$data = []; +$index = 0; +$currentthemeindex = 0; +$themes = core_component::get_plugin_list('theme'); + +// Loop through available themes. +foreach ($themes as $themename => $themedir) { + + try { + $theme = theme_config::load($themename); + } catch (Exception $e) { + // Bad theme, just skip it for now. + continue; + } + if ($themename !== $theme->name) { + // Obsoleted or broken theme, just skip for now. + continue; + } + if (empty($CFG->themedesignermode) && $theme->hidefromselector) { + // The theme doesn't want to be shown in the theme selector and as theme + // designer mode is switched off we will respect that decision. + continue; + } + + // All params for modal use are set here, except for 'choosereadme' (description). + // That string can be long. We will fetch it with JS as opposed to passing it as an attribute. + $themedata = []; + + // The 'name' param is formatted and should not to be confused with 'choose'. + $themedata['name'] = get_string('pluginname', 'theme_'.$themename);; + $themedata['choose'] = $themename; + + // Image to display for previewing. + $image = new moodle_url('/theme/image.php', ['theme' => $themename, 'image' => 'screenshot', 'component' => 'theme']); + $themedata['image'] = $image; + + // Is this the current theme? + if ($themename === $CFG->theme) { + $themedata['current'] = true; + $currentthemeindex = $index; + } else { + // Form params. + $actionurl = new moodle_url('/admin/themeselector.php'); + $themedata['actionurl'] = $actionurl; + $themedata['sesskey'] = sesskey(); + } + + $data[$index] = $themedata; + $index++; +} + +// Reorder the array to always have the current theme first. +if (isset($data[$currentthemeindex])) { + $currenttheme = $data[$currentthemeindex]; + unset($data[$currentthemeindex]); + array_unshift($data, $currenttheme); +} + +// Show theme selector. +$renderable = new \core_admin\output\theme_selector($data); +$renderer = $PAGE->get_renderer('core', 'admin'); +echo $renderer->theme_selector_list($renderable); + +// Show footer. +echo $OUTPUT->footer(); diff --git a/lang/en/moodle.php b/lang/en/moodle.php index a42ac45d231..c6f885529ea 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -483,6 +483,7 @@ $string['currentlanguage'] = 'Current language'; $string['currentlocaltime'] = 'your current local time'; $string['currentpicture'] = 'Current picture'; $string['currentrelease'] = 'Current release information'; +$string['currenttheme'] = 'Current theme'; $string['currentversion'] = 'Current version'; $string['databasechecking'] = 'Upgrading Moodle database from version {$a->oldversion} to {$a->newversion}'; $string['databaseperformance'] = 'Database performance'; @@ -1723,6 +1724,7 @@ $string['preprocessingbackupfile'] = 'Preprocessing backup file'; $string['prev'] = 'Prev'; $string['preview'] = 'Preview'; $string['previeworchoose'] = 'Preview or choose a theme'; +$string['previewthemename'] = 'Preview theme \'{$a}\''; $string['previous'] = 'Previous'; $string['previouslyselectedusers'] = 'Previously selected users not matching \'{$a}\''; $string['previouspage'] = 'Previous page'; @@ -2004,6 +2006,8 @@ $string['selectperiod'] = 'Select period'; $string['selectcategorysort'] = 'Which categories would you like to sort?'; $string['selectcategorysortby'] = 'Select how you would like to sort categories'; $string['selectcoursesortby'] = 'Select how you would like to sort courses'; +$string['selecttheme'] = 'Select theme'; +$string['selectthemename'] = 'Select theme \'{$a}\''; $string['senddetails'] = 'Send my details via email'; $string['sent'] = 'Sent'; $string['separate'] = 'Separate'; @@ -2242,6 +2246,8 @@ $string['timesplitting:tenpercentafterstart'] = '10% after start'; $string['timesplitting:tenpercentafterstart_help'] = 'This analysis interval generates a prediction after the 10% of the course is completed.'; $string['thanks'] = 'Thanks'; $string['theme'] = 'Theme'; +$string['themepreviewdescription'] = '{$a} theme description'; +$string['themepreviewimage'] = '{$a} preview image'; $string['themes'] = 'Themes'; $string['themesaved'] = 'New theme saved'; $string['thereareno'] = 'There are no {$a} in this course'; diff --git a/lib/classes/plugininfo/theme.php b/lib/classes/plugininfo/theme.php index fbc1125ceeb..79269b9d095 100644 --- a/lib/classes/plugininfo/theme.php +++ b/lib/classes/plugininfo/theme.php @@ -91,6 +91,6 @@ class theme extends base { * @return moodle_url */ public static function get_manage_url() { - return new moodle_url('/theme/index.php'); + return new moodle_url('/admin/themeselector.php'); } } diff --git a/theme/boost/scss/moodle/admin.scss b/theme/boost/scss/moodle/admin.scss index 174e7aace6d..c1aba6f3113 100644 --- a/theme/boost/scss/moodle/admin.scss +++ b/theme/boost/scss/moodle/admin.scss @@ -367,14 +367,6 @@ width: 4em; } -#adminthemeselector table { - border-collapse: collapse; -} - -#adminthemeselector .selectedtheme { - border: 1px solid $state-info-border; -} - .admin_colourpicker, .admin_colourpicker_preview { display: none; diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index d67b4ff7ae9..c355dd76904 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -26674,14 +26674,6 @@ body.behat-site .action-menu .dropdown-subpanel-content.show { width: 4em; } -#adminthemeselector table { - border-collapse: collapse; -} - -#adminthemeselector .selectedtheme { - border: 1px solid #b8dce2; -} - .admin_colourpicker, .admin_colourpicker_preview { display: none; diff --git a/theme/boost/tests/behat/theme_selector.feature b/theme/boost/tests/behat/theme_selector.feature new file mode 100644 index 00000000000..f7ffc0d17ae --- /dev/null +++ b/theme/boost/tests/behat/theme_selector.feature @@ -0,0 +1,28 @@ +@core @core_admin @theme_boost +Feature: Select a theme in Boost theme + In order to choose a theme + As an admin + I need to preview the theme and make a selection + + Background: + Given I log in as "admin" + And I navigate to "Appearance > Themes > Theme selector" in site administration + + @javascript + Scenario: I am able to preview a theme using a modal window + When I click on "Preview theme 'Boost'" "button" + Then I should see "Boost" in the "Boost" "dialogue" + And I should see "Boost is a modern highly-customisable theme." in the "Boost" "dialogue" + And I should see "Current theme" in the "Boost" "dialogue" + + @javascript + Scenario: I am able to change the theme using the modal window select button + Given I should see "Current theme" in the "#theme-card-boost" "css_element" + When I click on "Preview theme 'Classic'" "button" + And I click on "Select theme" "button" in the "Classic" "dialogue" + Then I should see "Current theme" in the "#theme-card-classic" "css_element" + + Scenario: I am able to change the theme using the normal select button + Given I should see "Current theme" in the "#theme-card-boost" "css_element" + When I click on "Select theme 'Classic'" "button" + Then I should see "Current theme" in the "#theme-card-classic" "css_element" diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index f2ddf7d293b..105d90a8cab 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -26674,14 +26674,6 @@ body.behat-site .action-menu .dropdown-subpanel-content.show { width: 4em; } -#adminthemeselector table { - border-collapse: collapse; -} - -#adminthemeselector .selectedtheme { - border: 1px solid #b8dce2; -} - .admin_colourpicker, .admin_colourpicker_preview { display: none; diff --git a/theme/classic/tests/behat/theme_selector.feature b/theme/classic/tests/behat/theme_selector.feature new file mode 100644 index 00000000000..75c8ac3774d --- /dev/null +++ b/theme/classic/tests/behat/theme_selector.feature @@ -0,0 +1,28 @@ +@core @core_admin @theme_classic +Feature: Select a theme in Classic theme + In order to choose a theme + As an admin + I need to preview the theme and make a selection + + Background: + Given I log in as "admin" + And I navigate to "Appearance > Themes > Theme selector" in site administration + + @javascript + Scenario: I am able to preview a theme using a modal window + When I click on "Preview theme 'Classic'" "button" + Then I should see "Classic" in the "Classic" "dialogue" + And I should see "Classic is a highly-customisable theme," in the "Classic" "dialogue" + And I should see "Current theme" in the "Classic" "dialogue" + + @javascript + Scenario: I am able to change the theme using the modal window select button + Given I should see "Current theme" in the "#theme-card-classic" "css_element" + When I click on "Preview theme 'Boost'" "button" + And I click on "Select theme" "button" in the "Boost" "dialogue" + Then I should see "Current theme" in the "#theme-card-boost" "css_element" + + Scenario: I am able to change the theme using the normal select button + Given I should see "Current theme" in the "#theme-card-classic" "css_element" + When I click on "Select theme 'Boost'" "button" + Then I should see "Current theme" in the "#theme-card-boost" "css_element" diff --git a/theme/index.php b/theme/index.php index 17e605dba1a..3bb694e3718 100644 --- a/theme/index.php +++ b/theme/index.php @@ -24,105 +24,5 @@ require_once(__DIR__ . '/../config.php'); require_once($CFG->libdir . '/adminlib.php'); -$choose = optional_param('choose', '', PARAM_PLUGIN); -$reset = optional_param('reset', 0, PARAM_BOOL); -$confirmation = optional_param('confirmation', 0, PARAM_BOOL); - -admin_externalpage_setup('themeselector'); - -unset($SESSION->theme); - -$PAGE->set_primary_active_tab('siteadminnode'); -$PAGE->navbar->add(get_string('themeselector', 'admin'), $PAGE->url); - -// Clear theme cache. -if ($reset and confirm_sesskey()) { - theme_reset_all_caches(); -} - -// Change theme. -if (!empty($choose) && confirm_sesskey()) { - - // Load the theme to make sure it is valid. - $theme = theme_config::load($choose); - - if ($theme instanceof \theme_config) { - set_config('theme', $theme->name); - $notifytype = 'success'; - $notifymessage = get_string('themesaved'); - } else { - $notifytype = 'error'; - $notifymessage = get_string('error'); - } - - // Redirect with notification. - redirect(new moodle_url('/theme/index.php'), $notifymessage, null, $notifytype); -} - -$table = new html_table(); -$table->data = []; -$table->id = 'adminthemeselector'; -$table->head = [get_string('theme'), get_string('info')]; -$table->align = ['left', 'left']; - -$themes = core_component::get_plugin_list('theme'); - -// Loop through available themes. -foreach ($themes as $themename => $themedir) { - - try { - $theme = theme_config::load($themename); - } catch (Exception $e) { - // Bad theme, just skip it for now. - continue; - } - if ($themename !== $theme->name) { - // Obsoleted or broken theme, just skip for now. - continue; - } - if (empty($CFG->themedesignermode) && $theme->hidefromselector) { - // The theme doesn't want to be shown in the theme selector and as theme - // designer mode is switched off we will respect that decision. - continue; - } - - // Build the table rows. - $row = []; - $rowclasses = []; - $strthemename = get_string('pluginname', 'theme_'.$themename); - - // Screenshot/preview cell. - $screenshotpath = new moodle_url('/theme/image.php', ['theme' => $themename, 'image' => 'screenshot', 'component' => 'theme']); - $row[] = html_writer::empty_tag('img', ['class' => 'img-fluid', 'src' => $screenshotpath, 'alt' => $strthemename]); - - // Info cell. - $infocell = $OUTPUT->heading($strthemename, 3); - - // Is this the current theme? - if ($themename == $CFG->theme) { - $rowclasses[] = 'selectedtheme'; - } else { - // Button to choose this as the main theme. - $setthemestr = get_string('usetheme'); - $setthemeurl = new moodle_url('/theme/index.php', ['choose' => $themename, 'sesskey' => sesskey()]); - $setthemebutton = new single_button($setthemeurl, $setthemestr, 'post'); - $infocell .= html_writer::div($OUTPUT->render($setthemebutton), 'mt-2'); - } - $row[] = $infocell; - - $table->data[$themename] = $row; - $table->rowclasses[$themename] = join(' ', $rowclasses); - $table->responsive = false; -} - -// Show heading. -echo $OUTPUT->header(); -echo $OUTPUT->heading(get_string('themeselector', 'admin')); - -// Reset theme caches button. -$reseturl = new moodle_url('index.php', ['sesskey' => sesskey(), 'reset' => 1]); -echo $OUTPUT->single_button($reseturl, get_string('themeresetcaches', 'admin'), 'post'); - -// Render main table. -echo html_writer::table($table); -echo $OUTPUT->footer(); +// Theme selector has been moved to admin/themeselector.php. +redirect (new moodle_url('/admin/themeselector.php')); diff --git a/theme/upgrade.txt b/theme/upgrade.txt index cf463b98d31..d7f16177da5 100644 --- a/theme/upgrade.txt +++ b/theme/upgrade.txt @@ -1,6 +1,11 @@ This files describes API changes in /theme/* themes, information provided here is intended especially for theme designer. +=== 4.4 === +* Theme selection is now performed using cards and modals. +* The 'choosereadme' string for each theme is expected to be plain text. HTML content will not render when displayed in the modal. +* Theme selector page has been moved to admin/themeselector.php (previously theme/index.php) + === 4.3 === * The core_renderer::htmllize_file_tree method has been deprecated. This was missed before Moodle 2.0. * CSS selectors using #categoryquestions have been changed to use .question-bank-table, see question/upgrade.txt