MDL-67883 core: Make core ready for MoodleNet.

This commit is contained in:
Mathew May 2020-06-05 10:09:37 +08:00
parent 111b293f61
commit 16d77f1884
32 changed files with 375 additions and 42 deletions

View File

@ -74,7 +74,4 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
new lang_string('configallowemojipickerincompatible', 'admin')
));
}
$optionalsubsystems->add(new admin_setting_configcheckbox('enablemoodlenet', new lang_string('enablemoodlenet', 'admin'),
new lang_string('enablemoodlenet_desc', 'admin'), 1, 1, 0));
}

View File

@ -186,6 +186,7 @@ if ($hassiteconfig
'email' => new lang_string('email'),
'city' => new lang_string('city'),
'country' => new lang_string('country'),
'moodlenetprofile' => new lang_string('moodlenetprofile', 'user'),
'timezone' => new lang_string('timezone'),
'webpage' => new lang_string('webpage'),
'icqnumber' => new lang_string('icqnumber'),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
define ("core_course/local/activitychooser/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.unfavouriteModule=a.favouriteModule=a.activityModules=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_course_get_course_content_items",args:{courseid:a}}])[0]};a.activityModules=c;var d=function(a,c){return b.default.call([{methodname:"core_course_add_content_item_to_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.favouriteModule=d;var e=function(a,c){return b.default.call([{methodname:"core_course_remove_content_item_from_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.unfavouriteModule=e});
define ("core_course/local/activitychooser/repository",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.fetchFooterData=a.unfavouriteModule=a.favouriteModule=a.activityModules=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){return b.default.call([{methodname:"core_course_get_course_content_items",args:{courseid:a}}])[0]};a.activityModules=c;var d=function(a,c){return b.default.call([{methodname:"core_course_add_content_item_to_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.favouriteModule=d;var e=function(a,c){return b.default.call([{methodname:"core_course_remove_content_item_from_user_favourites",args:{componentname:a,contentitemid:c}}])[0]};a.unfavouriteModule=e;var f=function(a,c){return b.default.call([{methodname:"core_course_get_activity_chooser_footer",args:{courseid:a,sectionid:c}}])[0]};a.fetchFooterData=f});
//# sourceMappingURL=repository.min.js.map

View File

@ -1 +1 @@
{"version":3,"sources":["../../../src/local/activitychooser/repository.js"],"names":["activityModules","courseid","ajax","call","methodname","args","favouriteModule","modName","modID","componentname","contentitemid","unfavouriteModule"],"mappings":"0NAsBA,uDASO,GAAMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAc,CAOzC,MAAOC,WAAKC,IAAL,CAAU,CAND,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAFM,CAMC,CAAV,EAAqB,CAArB,CACV,CARM,C,oBAmBA,GAAMK,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAUC,CAAV,CAAoB,CAQ/C,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,iDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C,oBAoBA,GAAMG,CAAAA,CAAiB,CAAG,SAACJ,CAAD,CAAUC,CAAV,CAAoB,CAQjD,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,sDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,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 <http://www.gnu.org/licenses/>.\n\n/**\n *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ajax from 'core/ajax';\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method activityModules\n * @param {Number} courseid What course to fetch the modules for\n * @return {object} jQuery promise\n */\nexport const activityModules = (courseid) => {\n const request = {\n methodname: 'core_course_get_course_content_items',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is a users' favourite.\n *\n * @method favouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const favouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_add_content_item_to_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is no longer a users' favourite.\n *\n * @method unfavouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const unfavouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_remove_content_item_from_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n"],"file":"repository.min.js"}
{"version":3,"sources":["../../../src/local/activitychooser/repository.js"],"names":["activityModules","courseid","ajax","call","methodname","args","favouriteModule","modName","modID","componentname","contentitemid","unfavouriteModule","fetchFooterData","sectionid"],"mappings":"4OAsBA,uDASO,GAAMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAc,CAOzC,MAAOC,WAAKC,IAAL,CAAU,CAND,CACZC,UAAU,CAAE,sCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAFM,CAMC,CAAV,EAAqB,CAArB,CACV,CARM,C,oBAmBA,GAAMK,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAUC,CAAV,CAAoB,CAQ/C,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,iDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C,oBAoBA,GAAMG,CAAAA,CAAiB,CAAG,SAACJ,CAAD,CAAUC,CAAV,CAAoB,CAQjD,MAAON,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,sDADA,CAEZC,IAAI,CAAE,CACFI,aAAa,CAAEF,CADb,CAEFG,aAAa,CAAEF,CAFb,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,C,sBAmBA,GAAMI,CAAAA,CAAe,CAAG,SAACX,CAAD,CAAWY,CAAX,CAAyB,CAQpD,MAAOX,WAAKC,IAAL,CAAU,CAPD,CACZC,UAAU,CAAE,yCADA,CAEZC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAEFY,SAAS,CAAEA,CAFT,CAFM,CAOC,CAAV,EAAqB,CAArB,CACV,CATM,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 <http://www.gnu.org/licenses/>.\n\n/**\n *\n * @module core_course/repository\n * @package core_course\n * @copyright 2019 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ajax from 'core/ajax';\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method activityModules\n * @param {Number} courseid What course to fetch the modules for\n * @return {object} jQuery promise\n */\nexport const activityModules = (courseid) => {\n const request = {\n methodname: 'core_course_get_course_content_items',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is a users' favourite.\n *\n * @method favouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const favouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_add_content_item_to_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Given a module name, module ID & the current course we want to specify that the module\n * is no longer a users' favourite.\n *\n * @method unfavouriteModule\n * @param {String} modName Frankenstyle name of the component to add favourite\n * @param {int} modID ID of the module. Mainly for LTI cases where they have same / similar names\n * @return {object} jQuery promise\n */\nexport const unfavouriteModule = (modName, modID) => {\n const request = {\n methodname: 'core_course_remove_content_item_from_user_favourites',\n args: {\n componentname: modName,\n contentitemid: modID,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Fetch all the information on modules we'll need in the activity chooser.\n *\n * @method fetchFooterData\n * @param {Number} courseid What course to fetch the data for\n * @param {Number} sectionid What section to fetch the data for\n * @return {object} jQuery promise\n */\nexport const fetchFooterData = (courseid, sectionid) => {\n const request = {\n methodname: 'core_course_get_activity_chooser_footer',\n args: {\n courseid: courseid,\n sectionid: sectionid,\n },\n };\n return ajax.call([request])[0];\n};\n"],"file":"repository.min.js"}

View File

@ -85,6 +85,20 @@ const registerListenerEvents = (courseId, chooserConfig) => {
};
})();
const fetchFooterData = (() => {
let footerInnerPromise = null;
return (sectionId) => {
if (!footerInnerPromise) {
footerInnerPromise = new Promise((resolve) => {
resolve(Repository.fetchFooterData(courseId, sectionId));
});
}
return footerInnerPromise;
};
})();
CustomEvents.define(document, events);
// Display module chooser event listeners.
@ -115,7 +129,8 @@ const registerListenerEvents = (courseId, chooserConfig) => {
bodyPromiseResolver = resolve;
});
const sectionModal = buildModal(bodyPromise);
const footerData = await fetchFooterData(caller.dataset.sectionid);
const sectionModal = buildModal(bodyPromise, footerData);
// Now we have a modal we should start fetching data.
const data = await fetchModuleData();
@ -127,6 +142,7 @@ const registerListenerEvents = (courseId, chooserConfig) => {
sectionModal,
builtModuleData,
partiallyAppliedFavouriteManager(data, caller.dataset.sectionid),
footerData,
);
bodyPromiseResolver(await Templates.render(
@ -221,13 +237,15 @@ const templateDataBuilder = (data, chooserConfig) => {
*
* @method buildModal
* @param {Promise} bodyPromise
* @param {String|Boolean} footer Either a footer to add or nothing
* @return {Object} The modal ready to display immediately and render body in later.
*/
const buildModal = bodyPromise => {
const buildModal = (bodyPromise, footer) => {
return ModalFactory.create({
type: ModalFactory.types.DEFAULT,
title: getString('addresourceoractivity'),
body: bodyPromise,
footer: footer.customfootertemplate,
large: true,
templateContext: {
classes: 'modchooser'

View File

@ -31,6 +31,7 @@ import {addIconToContainer} from 'core/loadingicon';
import * as Repository from 'core_course/local/activitychooser/repository';
import Notification from 'core/notification';
import {debounce} from 'core/utils';
const getPlugin = pluginName => import(pluginName);
/**
* Given an event from the main module 'page' navigate to it's help section via a carousel.
@ -38,8 +39,13 @@ import {debounce} from 'core/utils';
* @method showModuleHelp
* @param {jQuery} carousel Our initialized carousel to manipulate
* @param {Object} moduleData Data of the module to carousel to
* @param {jQuery} modal We need to figure out if the current modal has a footer.
*/
const showModuleHelp = (carousel, moduleData) => {
const showModuleHelp = (carousel, moduleData, modal = null) => {
// If we have a real footer then we need to change temporarily.
if (modal !== null && moduleData.showFooter === true) {
modal.setFooter(Templates.render('core_course/local/activitychooser/footer_partial', moduleData));
}
const help = carousel.find(selectors.regions.help)[0];
help.innerHTML = '';
help.classList.add('m-auto');
@ -107,8 +113,9 @@ const manageFavouriteState = async(modalBody, caller, partialFavourite) => {
* @param {Promise} modal Our modal that we are working with
* @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}
* @param {Function} partialFavourite Partially applied function we need to manage favourite status
* @param {Object} footerData Our base footer object.
*/
const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
const registerListenerEvents = (modal, mappedModules, partialFavourite, footerData) => {
const bodyClickListener = async(e) => {
if (e.target.closest(selectors.actions.optionActions.showSummary)) {
const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));
@ -116,7 +123,9 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
const module = e.target.closest(selectors.regions.chooserOption.container);
const moduleName = module.dataset.modname;
const moduleData = mappedModules.get(moduleName);
showModuleHelp(carousel, moduleData);
// We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.
moduleData.showFooter = modal.hasFooterContent();
showModuleHelp(carousel, moduleData, modal);
}
if (e.target.closest(selectors.actions.optionActions.manageFavourite)) {
@ -128,7 +137,7 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
const firstChooserOption = sectionChooserOptions
.querySelector(selectors.regions.chooserOption.container);
toggleFocusableChooserOption(firstChooserOption, true);
initChooserOptionsKeyboardNavigation(modal.getBody()[0], mappedModules, sectionChooserOptions);
initChooserOptionsKeyboardNavigation(modal.getBody()[0], mappedModules, sectionChooserOptions, modal);
}
// From the help screen go back to the module overview.
@ -150,7 +159,18 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
const searchInput = modal.getBody()[0].querySelector(selectors.actions.search);
searchInput.value = "";
searchInput.focus();
toggleSearchResultsView(modal.getBody()[0], mappedModules, searchInput.value);
toggleSearchResultsView(modal, mappedModules, searchInput.value);
}
};
// We essentially have two types of footer.
// A fake one that is handled within the template for chooser_help and then all of the stuff for
// modal.footer. We need to ensure we know exactly what type of footer we are using so we know what we
// need to manage. The below code handles a real footer going to a mnet carousel item.
const footerClickListener = async(e) => {
if (footerData.footer === true) {
const footerjs = await getPlugin(footerData.customfooterjs);
await footerjs.footerClickListener(e, footerData, modal);
}
};
@ -183,7 +203,7 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
// The search input is triggered.
searchInput.addEventListener('input', debounce(() => {
// Display the search results.
toggleSearchResultsView(body, mappedModules, searchInput.value);
toggleSearchResultsView(modal, mappedModules, searchInput.value);
}, 300));
return body;
})
@ -197,12 +217,22 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
toggleFocusableChooserOption(firstChooserOption, true);
initTabsKeyboardNavigation(body);
initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions);
initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions, modal);
return body;
})
.catch();
modal.getFooterPromise()
// The return value of getBodyPromise is a jquery object containing the body NodeElement.
.then(footer => footer[0])
// Add the listener for clicks on the footer.
.then(footer => {
footer.addEventListener('click', footerClickListener);
return footer;
})
.catch();
};
/**
@ -283,8 +313,9 @@ const initTabsKeyboardNavigation = (body) => {
* @param {HTMLElement} body Our modal that we are working with
* @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}
* @param {HTMLElement} chooserOptionsContainer The section that contains the chooser items
* @param {Object} modal Our created modal for the section
*/
const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer) => {
const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer, modal = null) => {
const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container);
Array.from(chooserOptions).forEach((element) => {
@ -303,7 +334,10 @@ const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOption
pause: true,
keyboard: false
});
showModuleHelp(carousel, moduleData);
// We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.
moduleData.showFooter = modal.hasFooterContent();
showModuleHelp(carousel, moduleData, modal);
}
}
@ -424,11 +458,12 @@ const renderSearchResults = async(searchResultsContainer, searchResultsData) =>
* Toggle (display/hide) the search results depending on the value of the search query
*
* @method toggleSearchResultsView
* @param {HTMLElement} modalBody The body of the created modal for the section
* @param {Object} modal Our created modal for the section
* @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}
* @param {String} searchQuery The search query
*/
const toggleSearchResultsView = async(modalBody, mappedModules, searchQuery) => {
const toggleSearchResultsView = async(modal, mappedModules, searchQuery) => {
const modalBody = modal.getBody()[0];
const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults);
const chooserContainer = modalBody.querySelector(selectors.regions.chooser);
const clearSearchButton = modalBody.querySelector(selectors.elements.clearsearch);
@ -443,7 +478,7 @@ const toggleSearchResultsView = async(modalBody, mappedModules, searchQuery) =>
// Set the first result item to be focusable.
toggleFocusableChooserOption(firstSearchResultItem, true);
// Register keyboard events on the created search result items.
initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer);
initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer, modal);
}
// Display the "clear" search button in the activity chooser search bar.
searchIcon.classList.add('d-none');
@ -513,7 +548,7 @@ const setupKeyboardAccessibility = (modal, mappedModules) => {
disableFocusAllChooserOptions(prevActiveSectionChooserOptions);
// Enable the focus of the first chooser option in the current active section.
toggleFocusableChooserOption(firstChooserOption, true);
initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions);
initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions, modal);
});
return;
}).catch(Notification.exception);
@ -539,8 +574,9 @@ const disableFocusAllChooserOptions = (sectionChooserOptions) => {
* @param {Promise} modalPromise Our created modal for the section
* @param {Array} sectionModules An array of all of the built module information
* @param {Function} partialFavourite Partially applied function we need to manage favourite status
* @param {Object} footerData Our base footer object.
*/
export const displayChooser = (modalPromise, sectionModules, partialFavourite) => {
export const displayChooser = (modalPromise, sectionModules, partialFavourite, footerData) => {
// Make a map so we can quickly fetch a specific module's object for either rendering or searching.
const mappedModules = new Map();
sectionModules.forEach((module) => {
@ -549,7 +585,7 @@ export const displayChooser = (modalPromise, sectionModules, partialFavourite) =
// Register event listeners.
modalPromise.then(modal => {
registerListenerEvents(modal, mappedModules, partialFavourite);
registerListenerEvents(modal, mappedModules, partialFavourite, footerData);
// We want to focus on the first chooser option element as soon as the modal is opened.
setupKeyboardAccessibility(modal, mappedModules);

View File

@ -78,3 +78,22 @@ export const unfavouriteModule = (modName, modID) => {
};
return ajax.call([request])[0];
};
/**
* Fetch all the information on modules we'll need in the activity chooser.
*
* @method fetchFooterData
* @param {Number} courseid What course to fetch the data for
* @param {Number} sectionid What section to fetch the data for
* @return {object} jQuery promise
*/
export const fetchFooterData = (courseid, sectionid) => {
const request = {
methodname: 'core_course_get_activity_chooser_footer',
args: {
courseid: courseid,
sectionid: sectionid,
},
};
return ajax.call([request])[0];
};

View File

@ -0,0 +1,86 @@
<?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/>.
/**
* Activity Chooser footer data class.
*
* @package core
* @subpackage course
* @copyright 2020 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\local\entity;
/**
* A class to represent the Activity Chooser footer data.
*
* @package core
* @subpackage course
* @copyright 2020 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity_chooser_footer {
/** @var string $footerjspath The path to the plugin JS file to dynamically import later. */
protected $footerjspath;
/** @var string $footertemplate The rendered template for the footer. */
protected $footertemplate;
/** @var string $carouseltemplate The rendered template for the footer. */
protected $carouseltemplate;
/**
* Constructor method.
*
* @param string $footerjspath JS file to dynamically import later.
* @param string $footertemplate Footer template that has been rendered.
* @param string|null $carouseltemplate Carousel template that may have been rendered.
*/
public function __construct(string $footerjspath, string $footertemplate, ?string $carouseltemplate = '') {
$this->footerjspath = $footerjspath;
$this->footertemplate = $footertemplate;
$this->carouseltemplate = $carouseltemplate;
}
/**
* Get the footer JS file path for this plugin.
*
* @return string The JS file to call functions from.
*/
public function get_footer_js_file(): string {
return $this->footerjspath;
}
/**
* Get the footer rendered template for this plugin.
*
* @return string The template that has been rendered for the chooser footer.
*/
public function get_footer_template(): string {
return $this->footertemplate;
}
/**
* Get the carousel rendered template for this plugin.
*
* @return string The template that has been rendered for the chooser carousel.
*/
public function get_carousel_template(): string {
return $this->carouseltemplate;
}
}

View File

@ -4336,4 +4336,74 @@ class core_course_external extends external_api {
]
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function get_activity_chooser_footer_parameters() {
return new external_function_parameters([
'courseid' => new external_value(PARAM_INT, 'ID of the course', VALUE_REQUIRED),
'sectionid' => new external_value(PARAM_INT, 'ID of the section', VALUE_REQUIRED),
]);
}
/**
* Given a course ID we need to build up a footre for the chooser.
*
* @param int $courseid The course we want to fetch the modules for
* @param int $sectionid The section we want to fetch the modules for
* @return array
*/
public static function get_activity_chooser_footer(int $courseid, int $sectionid) {
[
'courseid' => $courseid,
'sectionid' => $sectionid,
] = self::validate_parameters(self::get_activity_chooser_footer_parameters(), [
'courseid' => $courseid,
'sectionid' => $sectionid,
]);
$coursecontext = context_course::instance($courseid);
self::validate_context($coursecontext);
$pluginswithfunction = get_plugins_with_function('custom_chooser_footer', 'lib.php');
if ($pluginswithfunction) {
foreach ($pluginswithfunction as $plugintype => $plugins) {
foreach ($plugins as $pluginfunction) {
$footerdata = $pluginfunction($courseid, $sectionid);
break; // Only a single plugin can modify the footer.
}
break; // Only a single plugin can modify the footer.
}
return [
'footer' => true,
'customfooterjs' => $footerdata->get_footer_js_file(),
'customfootertemplate' => $footerdata->get_footer_template(),
'customcarouseltemplate' => $footerdata->get_carousel_template(),
];
} else {
return [
'footer' => false,
];
}
}
/**
* Returns description of method result value
*
* @return external_description
*/
public static function get_activity_chooser_footer_returns() {
return new external_single_structure(
[
'footer' => new external_value(PARAM_BOOL, 'Is a footer being return by this request?', VALUE_REQUIRED),
'customfooterjs' => new external_value(PARAM_RAW, 'The path to the plugin JS file', VALUE_OPTIONAL),
'customfootertemplate' => new external_value(PARAM_RAW, 'The prerendered footer', VALUE_OPTIONAL),
'customcarouseltemplate' => new external_value(PARAM_RAW, 'Either "" or the prerendered carousel page',
VALUE_OPTIONAL),
]
);
}
}

View File

@ -147,5 +147,7 @@
</div>
</div>
<div class="carousel-item" data-region="help"></div>
<!--The following div is used as a place for additional plugins to have widgets in the chooser.-->
<div class="carousel-item" data-region="pluginCarousel"></div>
</div>
</div>

View File

@ -0,0 +1,33 @@
{{!
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/>.
}}
{{!
@template core_course/local/activitychooser/footer_partial
Chooser favourite template partial.
Example context (json):
{
}
}}
<div class="w-100 d-flex justify-content-between" data-region="chooser-option-summary-actions-container">
<button data-action="close-chooser-option-summary" class="closeoptionsummary btn btn-secondary" tabindex="0" data-modname="{{componentname}}_{{link}}">
{{#str}} back {{/str}}
</button>
<a href="{{link}}" title="{{#str}} addnew, moodle, {{title}} {{/str}}" data-action="add-chooser-option" class="addoption btn btn-primary" tabindex="0">
{{#str}} add {{/str}}
</a>
</div>

View File

@ -44,12 +44,9 @@
{{{help}}}
</div>
</div>
<div class="actions fixed-bottom w-100 d-flex justify-content-between position-absolute py-3 px-4" data-region="chooser-option-summary-actions-container">
<button data-action="close-chooser-option-summary" class="closeoptionsummary btn btn-secondary" tabindex="0" data-modname="{{componentname}}_{{link}}">
{{#str}} back {{/str}}
</button>
<a href="{{link}}" title="{{#str}} addnew, moodle, {{title}} {{/str}}" data-action="add-chooser-option" class="addoption btn btn-primary" tabindex="0">
{{#str}} add {{/str}}
</a>
</div>
{{^showFooter}}
<div class="fixed-bottom position-absolute py-3 px-4 border-top">
{{>core_course/local/activitychooser/footer_partial}}
</div>
{{/showFooter}}
</div>

View File

@ -14,6 +14,8 @@ Feature: Display and choose from the available activities in course
And the following "course enrolments" exist:
| user | course | role |
| teacher | C | editingteacher |
And the following config values are set as admin:
| enablemoodlenet | 0 | tool_moodlenet |
And I log in as "teacher"
And I am on "Course" course homepage with editing mode on

View File

@ -539,8 +539,6 @@ $string['enableglobalsearch_desc'] = 'If enabled, data will be indexed and synch
$string['enablegravatar'] = 'Enable Gravatar';
$string['enablegravatar_help'] = 'When enabled Moodle will attempt to fetch a user profile picture from Gravatar if the user has not uploaded an image.';
$string['enablemobilewebservice'] = 'Enable web services for mobile devices';
$string['enablemoodlenet'] = 'Enable integration with MoodleNet instances';
$string['enablemoodlenet_desc'] = 'If enabled, and provided the MoodleNet plugin is installed, users can import content from MoodleNet into this site.';
$string['enablerecordcache'] = 'Enable record cache';
$string['enablerssfeeds'] = 'Enable RSS feeds';
$string['enablesearchareas'] = 'Enable search areas';

View File

@ -33,6 +33,7 @@ $string['countparticipantsfound'] = '{$a} participants found';
$string['filtersetmatchdescription'] = 'How multiple filters should be combined';
$string['match'] = 'Match';
$string['matchofthefollowing'] = 'of the following:';
$string['moodlenetprofile'] = 'MoodleNet profile';
$string['placeholdertypeorselect'] = 'Type or select...';
$string['placeholdertype'] = 'Type...';
$string['privacy:courserequestpath'] = 'Requested courses';
@ -87,6 +88,7 @@ $string['privacy:metadata:maildisplay'] = 'A preference for the user about displ
$string['privacy:metadata:middlename'] = 'The middle name of the user';
$string['privacy:metadata:mnethostid'] = 'An identifier for the MNet host if used';
$string['privacy:metadata:model'] = 'The device name, occam or iPhone etc..';
$string['privacy:metadata:moodlenetprofile'] = 'The MoodleNet profile for the user';
$string['privacy:metadata:msn'] = 'The MSN identifier of the user';
$string['privacy:metadata:my_pages'] = 'User pages - dashboard and profile. This table does not contain personal data and only used to link dashboard blocks to users';
$string['privacy:metadata:my_pages:name'] = 'Page name';

View File

@ -872,6 +872,7 @@
<FIELD NAME="firstnamephonetic" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="First name phonetic"/>
<FIELD NAME="middlename" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Middle name"/>
<FIELD NAME="alternatename" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Alternate name - Useful for three-name countries."/>
<FIELD NAME="moodlenetprofile" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Moodle.net profile information"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>

View File

@ -671,6 +671,14 @@ $functions = array(
'type' => 'read',
'ajax' => true,
),
'core_course_get_activity_chooser_footer' => array(
'classname' => 'core_course_external',
'methodname' => 'get_activity_chooser_footer',
'classpath' => 'course/externallib.php',
'description' => 'Fetch the data for the activity chooser footer.',
'type' => 'read',
'ajax' => true,
),
'core_course_toggle_activity_recommendation' => array(
'classname' => 'core_course_external',
'methodname' => 'toggle_activity_recommendation',

View File

@ -2427,5 +2427,19 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2020052200.01);
}
if ($oldversion < 2020060500.01) {
// Define field moodlenetprofile to be added to user.
$table = new xmldb_table('user');
$field = new xmldb_field('moodlenetprofile', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'alternatename');
// Conditionally launch add field moodlenetprofile.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2020060500.01);
}
return true;
}

View File

@ -165,6 +165,12 @@ function core_myprofile_navigation(core_user\output\myprofile\tree $tree, $user,
$tree->add_node($node);
}
if (!isset($hiddenfields['moodlenetprofile']) && $user->moodlenetprofile) {
$node = new core_user\output\myprofile\node('contact', 'moodlenetprofile', get_string('moodlenetprofile', 'user'), null,
null, $user->moodlenetprofile);
$tree->add_node($node);
}
if (!isset($hiddenfields['country']) && $user->country) {
$node = new core_user\output\myprofile\node('contact', 'country', get_string('country'), null, null,
get_string($user->country, 'countries'));

View File

@ -318,6 +318,7 @@ function url_dndupload_handle($uploadinfo) {
$data->introformat = FORMAT_HTML;
$data->externalurl = clean_param($uploadinfo->content, PARAM_URL);
$data->timemodified = time();
$data->coursemodule = $uploadinfo->coursemodule;
// Set the display options to the site defaults.
$config = get_config('url');

BIN
pix/MoodleNet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

1
pix/MoodleNet.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 934.36 169.63"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path d="M762.37,40.25a8.45,8.45,0,0,0-8.44,8.44v88.4l-72-91.84c-1.9-2.41-4.22-4.65-8.28-4.65H671.7A8.56,8.56,0,0,0,663.26,49V160a8.37,8.37,0,0,0,8.27,8.44A8.45,8.45,0,0,0,680,160V69.11l73.63,94.16c2.13,2.48,4.62,4.78,8.42,4.78h.7a7.83,7.83,0,0,0,7.92-8.09V48.69A8.37,8.37,0,0,0,762.37,40.25Z" style="fill:#414443"/><path d="M872.92,119.11c0-23.7-15.06-47.7-43.83-47.7a43.32,43.32,0,0,0-32.44,14.36c-8.25,9.1-12.8,21.37-12.8,34.58v.35c0,27.89,20.21,48.93,47,48.93,14.28,0,25.21-4.37,35.37-14.12a7.4,7.4,0,0,0,2.65-5.59,7.43,7.43,0,0,0-12.5-5.38c-7.52,6.92-15.51,10.15-25.17,10.15-16,0-27.93-11-30.27-27.66H865A7.92,7.92,0,0,0,872.92,119.11ZM828.74,86c11.75,0,24.9,7.35,27.31,27.83H800.93C803.23,97.36,814.46,86,828.74,86Z" style="fill:#414443"/><path d="M926.62,152.4a7.44,7.44,0,0,0-1.88.35,23.35,23.35,0,0,1-6.39.88c-9.38,0-13.75-4.31-13.75-13.57V88.47h22a7.66,7.66,0,0,0,7.74-7.56,7.75,7.75,0,0,0-7.74-7.56h-22V53.44A8.55,8.55,0,0,0,896.16,45a8.27,8.27,0,0,0-8.26,8.44V73.35h-5.82a7.66,7.66,0,0,0-7.56,7.56,7.75,7.75,0,0,0,7.56,7.56h5.82v53.35c0,17.6,9.69,27.28,27.28,27.28a35.66,35.66,0,0,0,13.94-2.57,7.23,7.23,0,0,0,4.89-6.75A7.4,7.4,0,0,0,926.62,152.4Z" style="fill:#414443"/><path d="M153.57,164.26V106.85q0-18-14.87-18t-14.88,18v57.41H94.6V106.85q0-18-14.62-18-14.88,0-14.87,18v57.41H35.88v-60.8q0-18.78,13-28.43Q60.41,66.41,80,66.41q19.83,0,29.23,10.18,8.08-10.19,29.49-10.18,19.56,0,31,8.62,13,9.64,13,28.43v60.8Z" style="fill:#f98012"/><path d="M511.75,164V0H541V164Z" style="fill:#f98012"/><path d="M474.47,164v-9.66q-3.92,5.22-13.32,8.36a49.12,49.12,0,0,1-15.93,2.87q-20.89,0-33.55-14.37T399,115.68c0-13.92,4.11-25.61,12.41-35,7.34-8.3,19.28-14.1,33-14.1,15.49,0,24.54,5.82,30,12.53V0h28.46V164Zm0-54.57q0-7.85-7.44-15t-15.28-7.19A20.73,20.73,0,0,0,434,96.36q-5.75,8.1-5.74,19.84,0,11.49,5.74,19.59,6.54,9.41,17.76,9.4,6.79,0,14.75-6.4t8-13.19Z" style="fill:#f98012"/><path d="M343.91,166.6q-22.2,0-36.69-14.1t-14.5-36.3q0-22.19,14.5-36.29T343.91,65.8q22.18,0,36.82,14.11t14.62,36.29q0,22.2-14.62,36.3T343.91,166.6Zm0-77.29q-10.57,0-16.26,8a32.07,32.07,0,0,0-5.67,19q0,11,5.28,18.63a20.35,20.35,0,0,0,33.3,0q5.54-7.6,5.54-18.63t-5.28-18.63Q354.73,89.31,343.91,89.31Z" style="fill:#f98012"/><path d="M238.15,166.6q-22.2,0-36.69-14.1T187,116.2Q187,94,201.46,79.91T238.15,65.8q22.18,0,36.82,14.11t14.62,36.29q0,22.2-14.62,36.3T238.15,166.6Zm0-77.29q-10.56,0-16.26,8a32.08,32.08,0,0,0-5.68,19q0,11,5.29,18.63a20.35,20.35,0,0,0,33.3,0q5.55-7.6,5.55-18.63t-5.29-18.63Q249,89.31,238.15,89.31Z" style="fill:#f98012"/><path d="M575.64,125.08c.62,7,9.67,21.94,24.55,21.94,14.48,0,21.33-8.36,21.67-11.75l30.81-.27c-3.36,10.28-17,32.13-53,32.13-15,0-28.68-4.66-38.52-14s-14.75-21.46-14.75-36.43q0-23.25,14.75-36.95T599.4,66.07q25.59,0,40,17,13.32,15.67,13.32,42Zm47.78-18a29.09,29.09,0,0,0-7.82-15.4,22,22,0,0,0-15.68-6.53,20.53,20.53,0,0,0-15.28,6.26,31.66,31.66,0,0,0-8.22,15.67Z" style="fill:#f98012"/><path d="M92.65,62l29-21.2-.37-1.29C68.94,45.9,45.11,50.45,0,76.6l.42,1.19,3.59,0a180.84,180.84,0,0,0-.17,26c-5,14.48-.13,24.33,4.45,35,.72-11.15.65-23.34-2.77-35.47a180.08,180.08,0,0,1,.19-25.51l29.91.29a135.7,135.7,0,0,0,.89,17.54c26.72,9.39,53.6,0,67.86-23.19C100.42,68,92.65,62,92.65,62Z" style="fill:#363636"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1637,6 +1637,17 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
}
}
.modchooser .modal-footer {
height: 70px;
.moodlenet-logo {
.icon {
height: 2.5rem;
width: 6rem;
margin-bottom: .6rem;
}
}
}
.modchoosercontainer.noscroll {
overflow-y: hidden;
}
@ -1684,7 +1695,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
background-color: $white;
overflow-x: hidden;
overflow-y: auto;
min-height: 640px;
height: 640px;
.content {
overflow-y: auto;
@ -2447,6 +2458,10 @@ body.h5p-embed {
overflow-wrap: break-word !important; /* stylelint-disable-line declaration-no-important */
}
.z-index-0 {
z-index: 0 !important; /* stylelint-disable-line declaration-no-important */
}
.z-index-1 {
z-index: 1 !important; /* stylelint-disable-line declaration-no-important */
}
@ -2653,4 +2668,4 @@ $picker-emojis-per-row: 7 !default;
position: relative;
z-index: inherit;
}
}
}

View File

@ -11001,6 +11001,13 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.modchooser .modal-body .carousel-item .loading-icon .icon {
margin: 1em auto; }
.modchooser .modal-footer {
height: 70px; }
.modchooser .modal-footer .moodlenet-logo .icon {
height: 2.5rem;
width: 6rem;
margin-bottom: .6rem; }
.modchoosercontainer.noscroll {
overflow-y: hidden; }
@ -11038,7 +11045,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
background-color: #fff;
overflow-x: hidden;
overflow-y: auto;
min-height: 640px; }
height: 640px; }
.modchooser .modal-body .optionsummary .content {
overflow-y: auto; }
.modchooser .modal-body .optionsummary .content .heading .icon {
@ -11631,6 +11638,10 @@ body.h5p-embed .h5pmessages {
overflow-wrap: break-word !important;
/* stylelint-disable-line declaration-no-important */ }
.z-index-0 {
z-index: 0 !important;
/* stylelint-disable-line declaration-no-important */ }
.z-index-1 {
z-index: 1 !important;
/* stylelint-disable-line declaration-no-important */ }

View File

@ -11208,6 +11208,13 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.modchooser .modal-body .carousel-item .loading-icon .icon {
margin: 1em auto; }
.modchooser .modal-footer {
height: 70px; }
.modchooser .modal-footer .moodlenet-logo .icon {
height: 2.5rem;
width: 6rem;
margin-bottom: .6rem; }
.modchoosercontainer.noscroll {
overflow-y: hidden; }
@ -11245,7 +11252,7 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
background-color: #fff;
overflow-x: hidden;
overflow-y: auto;
min-height: 640px; }
height: 640px; }
.modchooser .modal-body .optionsummary .content {
overflow-y: auto; }
.modchooser .modal-body .optionsummary .content .heading .icon {
@ -11842,6 +11849,10 @@ body.h5p-embed .h5pmessages {
overflow-wrap: break-word !important;
/* stylelint-disable-line declaration-no-important */ }
.z-index-0 {
z-index: 0 !important;
/* stylelint-disable-line declaration-no-important */ }
.z-index-1 {
z-index: 1 !important;
/* stylelint-disable-line declaration-no-important */ }

View File

@ -104,7 +104,8 @@ class provider implements
'lastnamephonetic' => 'privacy:metadata:lastnamephonetic',
'firstnamephonetic' => 'privacy:metadata:firstnamephonetic',
'middlename' => 'privacy:metadata:middlename',
'alternatename' => 'privacy:metadata:alternatename'
'alternatename' => 'privacy:metadata:alternatename',
'moodlenetprofile' => 'privacy:metadata:moodlenetprofile'
];
$passwordhistory = [

View File

@ -300,6 +300,9 @@ function useredit_shared_definition(&$mform, $editoroptions, $filemanageroptions
$mform->setDefault('maildisplay', core_user::get_property_default('maildisplay'));
$mform->addHelpButton('maildisplay', 'emaildisplay');
$mform->addElement('text', 'moodlenetprofile', get_string('moodlenetprofile', 'user'));
$mform->setType('moodlenetprofile', PARAM_RAW_TRIMMED);
$mform->addElement('text', 'city', get_string('city'), 'maxlength="120" size="21"');
$mform->setType('city', PARAM_TEXT);
if (!empty($CFG->defaultcity)) {

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2020060200.01; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2020060500.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.9dev+ (Build: 20200602)'; // Human-friendly version name