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));
}
'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'),
};
})();
+ 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.
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();
sectionModal,
builtModuleData,
partiallyAppliedFavouriteManager(data, caller.dataset.sectionid),
+ footerData,
);
bodyPromiseResolver(await Templates.render(
*
* @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'
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.
* @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');
* @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));
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)) {
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.
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);
}
};
// 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;
})
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();
};
/**
* @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) => {
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);
}
}
* 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);
// 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');
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);
* @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) => {
// 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);
};
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];
+};
--- /dev/null
+<?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;
+ }
+}
]
);
}
+
+ /**
+ * 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),
+ ]
+ );
+ }
}
</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>
--- /dev/null
+{{!
+ 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>
{{{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>
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
$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';
$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';
$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';
<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"/>
'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',
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;
}
$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'));
$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');
--- /dev/null
+<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>
\ No newline at end of file
}
}
+.modchooser .modal-footer {
+ height: 70px;
+ .moodlenet-logo {
+ .icon {
+ height: 2.5rem;
+ width: 6rem;
+ margin-bottom: .6rem;
+ }
+ }
+}
+
.modchoosercontainer.noscroll {
overflow-y: hidden;
}
background-color: $white;
overflow-x: hidden;
overflow-y: auto;
- min-height: 640px;
+ height: 640px;
.content {
overflow-y: auto;
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 */
}
position: relative;
z-index: inherit;
}
-}
\ No newline at end of file
+}
.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; }
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 {
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 */ }
.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; }
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 {
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 */ }
'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 = [
$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)) {
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