diff --git a/editmode.php b/editmode.php new file mode 100644 index 00000000000..36c340d4f98 --- /dev/null +++ b/editmode.php @@ -0,0 +1,47 @@ +. + +/** + * Set Mode. + * + * @package core + * @copyright 2021 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once('config.php'); + +$setmode = optional_param('setmode', false, PARAM_BOOL); +$contextid = required_param('context', PARAM_INT); +$pageurl = required_param('pageurl', PARAM_LOCALURL); + +require_sesskey(); +require_login(); + +$context = \context_helper::instance_by_id($contextid); +$PAGE->set_context($context); + +if ($context->id === \context_user::instance($USER->id)->id) { + $PAGE->set_blocks_editing_capability('moodle/my:manageblocks'); +} + +if ($PAGE->user_allowed_editing()) { + $USER->editing = $setmode; +} else { + \core\notification::add(get_string('cannotswitcheditmodeon', 'error'), \core\notification::ERROR); +} + +redirect($pageurl); diff --git a/lang/en/error.php b/lang/en/error.php index f13a4ea308b..256461e2a6e 100644 --- a/lang/en/error.php +++ b/lang/en/error.php @@ -152,6 +152,7 @@ $string['cannotsetupblock'] = 'Blocks tables could NOT be set up successfully!'; $string['cannotsetupcapformod'] = 'Could not set up the capabilities for {$a}'; $string['cannotsetupcapforplugin'] = 'Could not set up the capabilities for {$a}'; $string['cannotshowhidecoursesincategory'] = 'Cannot show/hide the courses in category {$a}.'; +$string['cannotswitcheditmodeon'] = 'Could not switch edit mode on'; $string['cannotsignup'] = 'You cannot create a new account because you are already logged in as {$a}.'; $string['cannotunassigncap'] = 'Could not unassign deprecated capability {$a->cap} from role {$a->role}'; $string['cannotunassignrolefrom'] = 'Cannot unassign this user from role id: {$a}'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 577fd3648c9..6daaf13685a 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -579,6 +579,7 @@ $string['editfiles'] = 'Edit files'; $string['editgroupprofile'] = 'Edit group profile'; $string['editinga'] = 'Editing {$a}'; $string['editingteachershort'] = 'Editor'; +$string['editmode'] = 'Edit mode'; $string['editlock'] = 'This value cannot be edited!'; $string['editmyprofile'] = 'Edit profile'; $string['editorbgcolor'] = 'Background-color'; @@ -1901,6 +1902,7 @@ $string['serverlocaltime'] = 'Server\'s local time'; $string['sessionforceclean'] = 'As a security precaution, user-generated scripts have been disabled within this session.'; $string['sessiontimeoutsoon'] = 'Your session is about to time out. Do you want to extend your current session?'; $string['setcategorytheme'] = 'Set category theme'; +$string['setmode'] = 'Set mode'; $string['setpassword'] = 'Set password'; $string['setpasswordinstructions'] = 'Please enter your new password below, then save changes.'; $string['settings'] = 'Settings'; diff --git a/lib/amd/build/edit_switch.min.js b/lib/amd/build/edit_switch.min.js new file mode 100644 index 00000000000..23199e13042 --- /dev/null +++ b/lib/amd/build/edit_switch.min.js @@ -0,0 +1,2 @@ +define ("core/edit_switch",["exports","core/ajax","core/event_dispatcher","core/notification"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=a.eventTypes=void 0;var e=function(a,c){return(0,b.call)([{methodname:"core_change_editmode",args:{context:a,setmode:c}}])[0]},f=function(a){if(a.checked){a.setAttribute("aria-checked",!0)}else{a.setAttribute("aria-checked",!1)}var b=h(a,a.checked);if(!b.defaultPrevented){window.location=a.dataset.pageurl}},g={editModeSet:"core/edit_switch/editModeSet"};a.eventTypes=g;var h=function(a,b){return(0,c.dispatchEvent)(g.editModeSet,{editMode:b},a,{cancelable:!0})};a.init=function init(a){var b=document.getElementById(a);b.addEventListener("change",function(){e(b.dataset.context,b.checked).then(function(a){if(a.success){f(b)}else{b.checked=!1}}).catch(d.exception)})}}); +//# sourceMappingURL=edit_switch.min.js.map diff --git a/lib/amd/build/edit_switch.min.js.map b/lib/amd/build/edit_switch.min.js.map new file mode 100644 index 00000000000..0488c0d41d0 --- /dev/null +++ b/lib/amd/build/edit_switch.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/edit_switch.js"],"names":["setEditMode","context","setmode","methodname","args","toggleEditSwitch","editSwitch","checked","setAttribute","event","notifyEditModeSet","defaultPrevented","window","location","dataset","pageurl","eventTypes","editModeSet","container","editMode","cancelable","init","editingSwitchId","document","getElementById","addEventListener","then","result","success","catch","displayException"],"mappings":"6MAkCMA,CAAAA,CAAW,CAAG,SAACC,CAAD,CAAUC,CAAV,QAAsB,WAAU,CAAC,CACjDC,UAAU,CAAE,sBADqC,CAEjDC,IAAI,CAAE,CACFH,OAAO,CAAPA,CADE,CAEFC,OAAO,CAAPA,CAFE,CAF2C,CAAD,CAAV,EAMtC,CANsC,CAAtB,C,CAedG,CAAgB,CAAG,SAAAC,CAAU,CAAI,CACnC,GAAIA,CAAU,CAACC,OAAf,CAAwB,CACpBD,CAAU,CAACE,YAAX,CAAwB,cAAxB,IACH,CAFD,IAEO,CACHF,CAAU,CAACE,YAAX,CAAwB,cAAxB,IACH,CAED,GAAMC,CAAAA,CAAK,CAAGC,CAAiB,CAACJ,CAAD,CAAaA,CAAU,CAACC,OAAxB,CAA/B,CACA,GAAI,CAACE,CAAK,CAACE,gBAAX,CAA6B,CACzBC,MAAM,CAACC,QAAP,CAAkBP,CAAU,CAACQ,OAAX,CAAmBC,OACxC,CACJ,C,CAQYC,CAAU,CAAG,CAUtBC,WAAW,CAAE,8BAVS,C,mBA0BpBP,CAAAA,CAAiB,CAAG,SAACQ,CAAD,CAAYC,CAAZ,QAAyB,oBAC/CH,CAAU,CAACC,WADoC,CAE/C,CAACE,QAAQ,CAARA,CAAD,CAF+C,CAG/CD,CAH+C,CAI/C,CAACE,UAAU,GAAX,CAJ+C,CAAzB,C,QAYN,QAAPC,CAAAA,IAAO,CAAAC,CAAe,CAAI,CACnC,GAAMhB,CAAAA,CAAU,CAAGiB,QAAQ,CAACC,cAAT,CAAwBF,CAAxB,CAAnB,CACAhB,CAAU,CAACmB,gBAAX,CAA4B,QAA5B,CAAsC,UAAM,CACxCzB,CAAW,CAACM,CAAU,CAACQ,OAAX,CAAmBb,OAApB,CAA6BK,CAAU,CAACC,OAAxC,CAAX,CACCmB,IADD,CACM,SAAAC,CAAM,CAAI,CACZ,GAAIA,CAAM,CAACC,OAAX,CAAoB,CAChBvB,CAAgB,CAACC,CAAD,CACnB,CAFD,IAEO,CACHA,CAAU,CAACC,OAAX,GACH,CAEJ,CARD,EASCsB,KATD,CASOC,WATP,CAUH,CAXD,CAYH,C","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 * Controls the edit switch.\n *\n * @module core/edit_switch\n * @copyright 2021 Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport {exception as displayException} from 'core/notification';\n\n/**\n * Change the Edit mode.\n *\n * @param {number} context The contextid that editing is being set for\n * @param {bool} setmode Whether editing is set or not\n * @return {Promise} Resolved with an array file the stored file url.\n */\nconst setEditMode = (context, setmode) => fetchMany([{\n methodname: 'core_change_editmode',\n args: {\n context,\n setmode,\n },\n}])[0];\n\n/**\n * Toggle the edit switch\n *\n * @method\n * @protected\n * @param {HTMLElement} editSwitch\n */\nconst toggleEditSwitch = editSwitch => {\n if (editSwitch.checked) {\n editSwitch.setAttribute('aria-checked', true);\n } else {\n editSwitch.setAttribute('aria-checked', false);\n }\n\n const event = notifyEditModeSet(editSwitch, editSwitch.checked);\n if (!event.defaultPrevented) {\n window.location = editSwitch.dataset.pageurl;\n }\n};\n\n/**\n * Names of events for core/edit_switch.\n *\n * @static\n * @property {String} editModeSet See {@link event:core/edit_switch/editModeSet}\n */\nexport const eventTypes = {\n /**\n * An event triggered when the edit mode toggled.\n *\n * @event core/edit_switch/editModeSet\n * @type {CustomEvent}\n * @property {HTMLElement} target The switch used to toggle the edit mode\n * @property {object} detail\n * @property {bool} detail.editMode\n */\n editModeSet: 'core/edit_switch/editModeSet',\n};\n\n/**\n * Dispatch the editModeSet event after changing the edit mode.\n *\n * This event is cancelable.\n *\n * The default action is to reload the page after toggling the edit mode.\n *\n * @method\n * @protected\n * @param {HTMLElement} container\n * @param {bool} editMode\n * @returns {CustomEvent}\n */\nconst notifyEditModeSet = (container, editMode) => dispatchEvent(\n eventTypes.editModeSet,\n {editMode},\n container,\n {cancelable: true}\n);\n\n/**\n * Add the eventlistener for the editswitch.\n *\n * @param {string} editingSwitchId The id of the editing switch to listen for\n */\nexport const init = editingSwitchId => {\n const editSwitch = document.getElementById(editingSwitchId);\n editSwitch.addEventListener('change', () => {\n setEditMode(editSwitch.dataset.context, editSwitch.checked)\n .then(result => {\n if (result.success) {\n toggleEditSwitch(editSwitch);\n } else {\n editSwitch.checked = false;\n }\n return;\n })\n .catch(displayException);\n });\n};\n"],"file":"edit_switch.min.js"} \ No newline at end of file diff --git a/lib/amd/src/edit_switch.js b/lib/amd/src/edit_switch.js new file mode 100644 index 00000000000..e1bacd29108 --- /dev/null +++ b/lib/amd/src/edit_switch.js @@ -0,0 +1,121 @@ +// 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 . + +/** + * Controls the edit switch. + * + * @module core/edit_switch + * @copyright 2021 Bas Brands + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {call as fetchMany} from 'core/ajax'; +import {dispatchEvent} from 'core/event_dispatcher'; +import {exception as displayException} from 'core/notification'; + +/** + * Change the Edit mode. + * + * @param {number} context The contextid that editing is being set for + * @param {bool} setmode Whether editing is set or not + * @return {Promise} Resolved with an array file the stored file url. + */ +const setEditMode = (context, setmode) => fetchMany([{ + methodname: 'core_change_editmode', + args: { + context, + setmode, + }, +}])[0]; + +/** + * Toggle the edit switch + * + * @method + * @protected + * @param {HTMLElement} editSwitch + */ +const toggleEditSwitch = editSwitch => { + if (editSwitch.checked) { + editSwitch.setAttribute('aria-checked', true); + } else { + editSwitch.setAttribute('aria-checked', false); + } + + const event = notifyEditModeSet(editSwitch, editSwitch.checked); + if (!event.defaultPrevented) { + window.location = editSwitch.dataset.pageurl; + } +}; + +/** + * Names of events for core/edit_switch. + * + * @static + * @property {String} editModeSet See {@link event:core/edit_switch/editModeSet} + */ +export const eventTypes = { + /** + * An event triggered when the edit mode toggled. + * + * @event core/edit_switch/editModeSet + * @type {CustomEvent} + * @property {HTMLElement} target The switch used to toggle the edit mode + * @property {object} detail + * @property {bool} detail.editMode + */ + editModeSet: 'core/edit_switch/editModeSet', +}; + +/** + * Dispatch the editModeSet event after changing the edit mode. + * + * This event is cancelable. + * + * The default action is to reload the page after toggling the edit mode. + * + * @method + * @protected + * @param {HTMLElement} container + * @param {bool} editMode + * @returns {CustomEvent} + */ +const notifyEditModeSet = (container, editMode) => dispatchEvent( + eventTypes.editModeSet, + {editMode}, + container, + {cancelable: true} +); + +/** + * Add the eventlistener for the editswitch. + * + * @param {string} editingSwitchId The id of the editing switch to listen for + */ +export const init = editingSwitchId => { + const editSwitch = document.getElementById(editingSwitchId); + editSwitch.addEventListener('change', () => { + setEditMode(editSwitch.dataset.context, editSwitch.checked) + .then(result => { + if (result.success) { + toggleEditSwitch(editSwitch); + } else { + editSwitch.checked = false; + } + return; + }) + .catch(displayException); + }); +}; diff --git a/lib/classes/external/editmode.php b/lib/classes/external/editmode.php new file mode 100644 index 00000000000..06bca19447a --- /dev/null +++ b/lib/classes/external/editmode.php @@ -0,0 +1,103 @@ +. + +/** + * A web service to load the mapping of moodle pix names to fontawesome icon names. + * + * @package core + * @category external + * @copyright 2021 Bas Brands + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\external; + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; + +/** + * Web service to change the edit mode. + * + * @package core + * @copyright 2021 Bas Brands + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class editmode extends external_api { + + /** + * Description of the parameters suitable for the `change_editmode` function. + * + * @return external_function_parameters + */ + public static function change_editmode_parameters(): external_function_parameters { + return new external_function_parameters( + [ + 'setmode' => new external_value(PARAM_BOOL, 'Set edit mode to'), + 'context' => new external_value(PARAM_INT, 'Page context id') + ] + ); + } + + /** + * Save the image and return any warnings and the new image url + * + * @param bool $setmode the current edit mode + * @param int $contextid the current page context id + * @return array the new edit mode. + */ + public static function change_editmode(bool $setmode, int $contextid): array { + global $USER, $PAGE; + + $params = self::validate_parameters( + self::change_editmode_parameters(), + [ + 'setmode' => $setmode, + 'context' => $contextid + ] + ); + + $context = \context_helper::instance_by_id($params['context']); + self::validate_context($context); + $PAGE->set_context($context); + + if ($context->id === \context_user::instance($USER->id)->id) { + $PAGE->set_blocks_editing_capability('moodle/my:manageblocks'); + } + + $success = false; + if ($PAGE->user_allowed_editing()) { + $USER->editing = $setmode; + $success = true; + } + + return ['success' => $success]; + } + + /** + * Description of the return value for the `change_editmode` function. + * + * @return external_single_structure + */ + public static function change_editmode_returns(): external_single_structure { + $keys = [ + 'success' => new external_value(PARAM_BOOL, 'The edit mode was changed', VALUE_REQUIRED), + ]; + + return new external_single_structure($keys, 'editmode'); + } +} diff --git a/lib/db/services.php b/lib/db/services.php index 89b5fe07e61..dff501bf9b4 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -2779,6 +2779,13 @@ $functions = array( 'type' => 'write', 'ajax' => true, ], + 'core_change_editmode' => [ + 'classname' => 'core\external\editmode', + 'methodname' => 'change_editmode', + 'description' => 'Change the editing mode', + 'type' => 'write', + 'ajax' => true, + ], ); $services = array( diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 39b3533350c..bcf90e423a1 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -2748,6 +2748,9 @@ EOD; */ public function edit_button(moodle_url $url) { + if ($this->page->theme->haseditswitch == true) { + return; + } $url->param('sesskey', sesskey()); if ($this->page->user_is_editing()) { $url->param('edit', 'off'); @@ -2760,6 +2763,27 @@ EOD; return $this->single_button($url, $editstring); } + /** + * Create a navbar switch for toggling editing mode. + * + * @return string Html containing the edit switch + */ + public function edit_switch() { + if ($this->page->user_allowed_editing()) { + + $temp = (object) [ + 'legacyseturl' => (new moodle_url('/editmode.php'))->out(false), + 'pagecontextid' => $this->page->context->id, + 'pageurl' => $this->page->url, + 'sesskey' => sesskey(), + ]; + if ($this->page->user_is_editing()) { + $temp->checked = true; + } + return $this->render_from_template('core/editswitch', $temp); + } + } + /** * Returns HTML to display a simple button to close a window * diff --git a/lib/templates/editswitch.mustache b/lib/templates/editswitch.mustache new file mode 100644 index 00000000000..6c368d3e954 --- /dev/null +++ b/lib/templates/editswitch.mustache @@ -0,0 +1,63 @@ +{{! + 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 theme_boost/editswitch + + This template renders the top navbar. + + Example context (json): + { + "url": "http://localhost/", + "sesskey": "sesskey", + "edit": 1, + "adminedit": true, + "checked": "", + "string": "Edit on" + } +}} +
+
+ + + + + + +
+
+{{#js}} +require(['core/edit_switch'], function(editSwitch) { + editSwitch.init('{{uniqid}}-editingswitch'); +}); +{{/js}} diff --git a/theme/boost/templates/navbar.mustache b/theme/boost/templates/navbar.mustache index 3afad628389..d5c6f2c0b56 100644 --- a/theme/boost/templates/navbar.mustache +++ b/theme/boost/templates/navbar.mustache @@ -73,5 +73,6 @@ {{> core/user_menu }} {{/usermenu}} + {{{ output.edit_switch }}} diff --git a/version.php b/version.php index 4b53e01d492..eb0f963cf16 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2021092400.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2021092400.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.0dev (Build: 20210924)'; // Human-friendly version name