This commit is contained in:
Jun Pataleta 2020-06-05 11:52:36 +08:00
commit 618b13e1f5
87 changed files with 5262 additions and 41 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'),

View File

@ -0,0 +1,2 @@
define ("tool_moodlenet/instance_form",["tool_moodlenet/validator","tool_moodlenet/selectors","core/loadingicon","core/templates","core/notification","jquery"],function(a,b,c,d,e,f){var g=function(d){d.addEventListener("click",function(f){if(f.target.matches(b.action.submit)){var e=d.querySelector("[data-var=\"mnet-link\"]"),g=d.querySelector(b.region.spinner),h=document.querySelector(b.region.validationArea);g.classList.remove("d-none");var i=c.addIconToContainerWithPromise(g);a.validation(e).then(function(a){i.resolve();g.classList.add("d-none");if(a.result){e.classList.remove("is-invalid");e.classList.add("is-valid");h.innerText=a.message;h.classList.remove("text-error");h.classList.add("text-success");setTimeout(function(){window.location=a.domain},1e3)}else{e.classList.add("is-invalid");h.innerText=a.message;h.classList.add("text-error")}}).catch()}})},h=function(a,b,h,i){a.innerHTML="";var j=c.addIconToContainer(a),k=null,l=new Promise(function(a){k=a});f.when(j,l).then(function(){d.replaceNodeContents(a,b.customcarouseltemplate,"")}).catch(e.exception);g(a);h.one("slid.bs.carousel",function(){k()});h.carousel(2);i.setFooter(d.render("tool_moodlenet/chooser_footer_close_mnet",{}))},i=function(a,b,c){a.carousel(0);b.setFooter(c.customfootertemplate)};return{footerClickListener:function footerClickListener(a,c,d){if(a.target.matches(b.action.showMoodleNet)||a.target.closest(b.action.showMoodleNet)){a.preventDefault();var e=f(d.getBody()[0].querySelector(b.region.carousel)),g=e.find(b.region.moodleNet)[0];h(g,c,e,d)}if(a.target.matches(b.action.closeOption)){var j=f(d.getBody()[0].querySelector(b.region.carousel));i(j,d,c)}}}});
//# sourceMappingURL=instance_form.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
define ("tool_moodlenet/select_page",["core/ajax","core/templates","tool_moodlenet/selectors","core/notification"],function(a,b,c,d){var e,f=function(a){return b.renderPix("courses","tool_moodlenet").then(function(a){return a}).then(function(a){var c=document.createElement("div");c.innerHTML=a.trim();return b.render("core_course/no-courses",{nocoursesimg:c.firstChild.src})}).then(function(c,d){b.replaceNodeContents(a,c,d);a.classList.add("mx-auto");a.classList.add("w-25")})},g=function(a,c){return b.render("tool_moodlenet/view-cards",{courses:c}).then(function(c,d){b.replaceNodeContents(a,c,d);a.classList.remove("mx-auto");a.classList.remove("w-25")})},h=function(b,h,i){var j=h.querySelector(c.region.searchIcon),k=h.querySelector(c.region.clearIcon);if(""!==b){j.classList.add("d-none");k.parentElement.classList.remove("d-none")}else{j.classList.remove("d-none");k.parentElement.classList.add("d-none")}a.call([{methodname:"tool_moodlenet_search_courses",args:{searchvalue:b}}])[0].then(function(a){if(0===a.courses.length){return f(i)}else{a.courses.forEach(function(a){a.viewurl+="&id="+e});return g(i,a.courses)}}).catch(d.exception)},i=function(a){var b=a.querySelector(c.region.searchInput),d=a.querySelector(c.region.courses),e=a.querySelector(c.region.clearIcon);e.addEventListener("click",function(){b.value="";h("",a,d)});b.addEventListener("input",k(function(){h(b.value,a,d)},300))},j=function(a){var b=a.querySelector(c.region.courses);h("",a,b)},k=function(a,b,c){var d;return function(){var e=this,f=arguments,g=c&&!d;clearTimeout(d);d=setTimeout(function later(){d=null;if(!c){a.apply(e,f)}},b);if(g){a.apply(e,f)}}};return{init:function init(a){e=a;var b=document.querySelector(c.region.selectPage);i(b);j(b)}}});
//# sourceMappingURL=select_page.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
define ("tool_moodlenet/selectors",[],function(){return{action:{browse:"[data-action=\"browse\"]",submit:"[data-action=\"submit\"]",showMoodleNet:"[data-action=\"show-moodlenet\"]",closeOption:"[data-action=\"close-chooser-option-summary\"]"},region:{clearIcon:"[data-region=\"clear-icon\"]",courses:"[data-region=\"mnet-courses\"]",instancePage:"[data-region=\"moodle-net\"]",searchInput:"[data-region=\"search-input\"]",searchIcon:"[data-region=\"search-icon\"]",selectPage:"[data-region=\"moodle-net-select\"]",spinner:"[data-region=\"spinner\"]",validationArea:"[data-region=\"validation-area\"]",carousel:"[data-region=\"carousel\"]",moodleNet:"[data-region=\"pluginCarousel\"]"}}});
//# sourceMappingURL=selectors.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../src/selectors.js"],"names":["define","action","browse","submit","showMoodleNet","closeOption","region","clearIcon","courses","instancePage","searchInput","searchIcon","selectPage","spinner","validationArea","carousel","moodleNet"],"mappings":"AAuBAA,OAAM,4BAAC,EAAD,CAAK,UAAW,CAClB,MAAO,CACHC,MAAM,CAAE,CACJC,MAAM,CAAE,0BADJ,CAEJC,MAAM,CAAE,0BAFJ,CAGJC,aAAa,CAAE,kCAHX,CAIJC,WAAW,CAAE,gDAJT,CADL,CAOHC,MAAM,CAAE,CACJC,SAAS,CAAE,8BADP,CAEJC,OAAO,CAAE,gCAFL,CAGJC,YAAY,CAAE,8BAHV,CAIJC,WAAW,CAAE,gCAJT,CAKJC,UAAU,CAAE,+BALR,CAMJC,UAAU,CAAE,qCANR,CAOJC,OAAO,CAAE,2BAPL,CAQJC,cAAc,CAAE,mCARZ,CASJC,QAAQ,CAAE,4BATN,CAUJC,SAAS,CAAE,kCAVP,CAPL,CAoBV,CArBK,CAAN","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 * Define all of the selectors we will be using within MoodleNet plugin.\n *\n * @module tool_moodlenet/selectors\n * @package tool_moodlenet\n * @copyright 2020 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n return {\n action: {\n browse: '[data-action=\"browse\"]',\n submit: '[data-action=\"submit\"]',\n showMoodleNet: '[data-action=\"show-moodlenet\"]',\n closeOption: '[data-action=\"close-chooser-option-summary\"]',\n },\n region: {\n clearIcon: '[data-region=\"clear-icon\"]',\n courses: '[data-region=\"mnet-courses\"]',\n instancePage: '[data-region=\"moodle-net\"]',\n searchInput: '[data-region=\"search-input\"]',\n searchIcon: '[data-region=\"search-icon\"]',\n selectPage: '[data-region=\"moodle-net-select\"]',\n spinner: '[data-region=\"spinner\"]',\n validationArea: '[data-region=\"validation-area\"]',\n carousel: '[data-region=\"carousel\"]',\n moodleNet: '[data-region=\"pluginCarousel\"]',\n },\n };\n});\n"],"file":"selectors.min.js"}

View File

@ -0,0 +1,2 @@
define ("tool_moodlenet/validator",["jquery","core/ajax","core/str","core/notification"],function(a,b,c,d){return{validation:function(e){var f=e.value;if(""===f||!f.includes("@")){a.when(c.get_string("profilevalidationerror","tool_moodlenet")).then(function(a){return Promise.reject().catch(function(){return{result:!1,message:a[0]}})}).fail(d.exception)}return b.call([{methodname:"tool_moodlenet_verify_webfinger",args:{profileurl:f,course:e.dataset.courseid,section:e.dataset.sectionid}}])[0].then(function(a){return a}).catch()}}});
//# sourceMappingURL=validator.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../src/validator.js"],"names":["define","$","Ajax","Str","Notification","validation","inputElement","inputValue","value","includes","when","get_string","then","strings","Promise","reject","catch","result","message","fail","exception","call","methodname","args","profileurl","course","dataset","courseid","section","sectionid"],"mappings":"AAuBAA,OAAM,4BAAC,CAAC,QAAD,CAAW,WAAX,CAAwB,UAAxB,CAAoC,mBAApC,CAAD,CAA2D,SAASC,CAAT,CAAYC,CAAZ,CAAkBC,CAAlB,CAAuBC,CAAvB,CAAqC,CAgClG,MAAO,CACHC,UAAU,CAzBG,SAAoBC,CAApB,CAAkC,CAC/C,GAAIC,CAAAA,CAAU,CAAGD,CAAY,CAACE,KAA9B,CAGA,GAAmB,EAAf,GAAAD,CAAU,EAAW,CAACA,CAAU,CAACE,QAAX,CAAoB,GAApB,CAA1B,CAAoD,CAEhDR,CAAC,CAACS,IAAF,CAAOP,CAAG,CAACQ,UAAJ,CAAe,wBAAf,CAAyC,gBAAzC,CAAP,EAAmEC,IAAnE,CAAwE,SAASC,CAAT,CAAkB,CACtF,MAAOC,CAAAA,OAAO,CAACC,MAAR,GAAiBC,KAAjB,CAAuB,UAAW,CACrC,MAAO,CAACC,MAAM,GAAP,CAAgBC,OAAO,CAAEL,CAAO,CAAC,CAAD,CAAhC,CACV,CAFM,CAGV,CAJD,EAIGM,IAJH,CAIQf,CAAY,CAACgB,SAJrB,CAKH,CAED,MAAOlB,CAAAA,CAAI,CAACmB,IAAL,CAAU,CAAC,CACdC,UAAU,CAAE,iCADE,CAEdC,IAAI,CAAE,CACFC,UAAU,CAAEjB,CADV,CAEFkB,MAAM,CAAEnB,CAAY,CAACoB,OAAb,CAAqBC,QAF3B,CAGFC,OAAO,CAAEtB,CAAY,CAACoB,OAAb,CAAqBG,SAH5B,CAFQ,CAAD,CAAV,EAOH,CAPG,EAOAjB,IAPA,CAOK,SAASK,CAAT,CAAiB,CACzB,MAAOA,CAAAA,CACV,CATM,EASJD,KATI,EAUV,CACM,CAGV,CAnCK,CAAN","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 * Our validator that splits the user's input then fires off to a webservice\n *\n * @module tool_moodlenet/validator\n * @package tool_moodlenet\n * @copyright 2020 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/str', 'core/notification'], function($, Ajax, Str, Notification) {\n /**\n * Handle form validation\n *\n * @method validation\n * @param {HTMLElement} inputElement The element the user entered text into.\n * @return {Promise} Was the users' entry a valid profile URL?\n */\n var validation = function validation(inputElement) {\n var inputValue = inputElement.value;\n\n // They didn't submit anything or they gave us a simple string that we can't do anything with.\n if (inputValue === \"\" || !inputValue.includes(\"@\")) {\n // Create a promise and immediately reject it.\n $.when(Str.get_string('profilevalidationerror', 'tool_moodlenet')).then(function(strings) {\n return Promise.reject().catch(function() {\n return {result: false, message: strings[0]};\n });\n }).fail(Notification.exception);\n }\n\n return Ajax.call([{\n methodname: 'tool_moodlenet_verify_webfinger',\n args: {\n profileurl: inputValue,\n course: inputElement.dataset.courseid,\n section: inputElement.dataset.sectionid\n }\n }])[0].then(function(result) {\n return result;\n }).catch();\n };\n return {\n validation: validation,\n };\n});\n"],"file":"validator.min.js"}

View File

@ -0,0 +1,169 @@
// 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/>.
/**
* Our basic form manager for when a user either enters
* their profile url or just wants to browse.
*
* This file is a mishmash of JS functions we need for both the standalone (M3.7, M3.8)
* plugin & Moodle 3.9 functions. The 3.9 Functions have a base understanding that certain
* things exist i.e. directory structures for templates. When this feature goes 3.9+ only
* The goal is that we can quickly gut all AMD modules into bare JS files and use ES6 guidelines.
* Till then this will have to do.
*
* @module tool_moodlenet/instance_form
* @package tool_moodlenet
* @copyright 2020 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['tool_moodlenet/validator',
'tool_moodlenet/selectors',
'core/loadingicon',
'core/templates',
'core/notification',
'jquery'],
function(Validator,
Selectors,
LoadingIcon,
Templates,
Notification,
$) {
/**
* Add the event listeners to our form.
*
* @method registerListenerEvents
* @param {HTMLElement} page The whole page element for our form area
*/
var registerListenerEvents = function registerListenerEvents(page) {
page.addEventListener('click', function(e) {
// Our fake submit button / browse button.
if (e.target.matches(Selectors.action.submit)) {
var input = page.querySelector('[data-var="mnet-link"]');
var overlay = page.querySelector(Selectors.region.spinner);
var validationArea = document.querySelector(Selectors.region.validationArea);
overlay.classList.remove('d-none');
var spinner = LoadingIcon.addIconToContainerWithPromise(overlay);
Validator.validation(input)
.then(function(result) {
spinner.resolve();
overlay.classList.add('d-none');
if (result.result) {
input.classList.remove('is-invalid'); // Just in case the class has been applied already.
input.classList.add('is-valid');
validationArea.innerText = result.message;
validationArea.classList.remove('text-error');
validationArea.classList.add('text-success');
// Give the user some time to see their input is valid.
setTimeout(function() {
window.location = result.domain;
}, 1000);
} else {
input.classList.add('is-invalid');
validationArea.innerText = result.message;
validationArea.classList.add('text-error');
}
return;
}).catch();
}
});
};
/**
* Given a user wishes to see the MoodleNet profile url form transition them there.
*
* @method chooserNavigateToMnet
* @param {HTMLElement} showMoodleNet The chooser's area for ment
* @param {Object} footerData Our footer object to render out
* @param {jQuery} carousel Our carousel instance to manage
* @param {jQuery} modal Our modal instance to manage
*/
var chooserNavigateToMnet = function(showMoodleNet, footerData, carousel, modal) {
showMoodleNet.innerHTML = '';
// Add a spinner.
var spinnerPromise = LoadingIcon.addIconToContainer(showMoodleNet);
// Used later...
var transitionPromiseResolver = null;
var transitionPromise = new Promise(resolve => {
transitionPromiseResolver = resolve;
});
$.when(
spinnerPromise,
transitionPromise
).then(function() {
Templates.replaceNodeContents(showMoodleNet, footerData.customcarouseltemplate, '');
return;
}).catch(Notification.exception);
// We apply our handlers in here to minimise plugin dependency in the Chooser.
registerListenerEvents(showMoodleNet);
// Move to the next slide, and resolve the transition promise when it's done.
carousel.one('slid.bs.carousel', function() {
transitionPromiseResolver();
});
// Trigger the transition between 'pages'.
carousel.carousel(2);
// eslint-disable-next-line max-len
modal.setFooter(Templates.render('tool_moodlenet/chooser_footer_close_mnet', {}));
};
/**
* Given a user no longer wishes to see the MoodleNet profile url form transition them from there.
*
* @method chooserNavigateFromMnet
* @param {jQuery} carousel Our carousel instance to manage
* @param {jQuery} modal Our modal instance to manage
* @param {Object} footerData Our footer object to render out
*/
var chooserNavigateFromMnet = function(carousel, modal, footerData) {
// Trigger the transition between 'pages'.
carousel.carousel(0);
modal.setFooter(footerData.customfootertemplate);
};
/**
* Create the custom listener that would handle anything in the footer.
*
* @param {Event} e The event being triggered.
* @param {Object} footerData The data generated from the exporter.
* @param {Object} modal The chooser modal.
*/
var footerClickListener = function(e, footerData, modal) {
if (e.target.matches(Selectors.action.showMoodleNet) || e.target.closest(Selectors.action.showMoodleNet)) {
e.preventDefault();
const carousel = $(modal.getBody()[0].querySelector(Selectors.region.carousel));
const showMoodleNet = carousel.find(Selectors.region.moodleNet)[0];
chooserNavigateToMnet(showMoodleNet, footerData, carousel, modal);
}
// From the help screen go back to the module overview.
if (e.target.matches(Selectors.action.closeOption)) {
const carousel = $(modal.getBody()[0].querySelector(Selectors.region.carousel));
chooserNavigateFromMnet(carousel, modal, footerData);
}
};
return {
footerClickListener: footerClickListener
};
});

View File

@ -0,0 +1,198 @@
// 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/>.
/**
* When returning to Moodle let the user select which course to add the resource to.
*
* @module tool_moodlenet/select_page
* @package tool_moodlenet
* @copyright 2020 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'core/ajax',
'core/templates',
'tool_moodlenet/selectors',
'core/notification'
], function(
Ajax,
Templates,
Selectors,
Notification
) {
/**
* @var {string} The id corresponding to the import.
*/
var importId;
/**
* Set up the page.
*
* @method init
* @param {string} importIdString the string ID of the import.
*/
var init = function(importIdString) {
importId = importIdString;
var page = document.querySelector(Selectors.region.selectPage);
registerListenerEvents(page);
addCourses(page);
};
/**
* Renders the 'no-courses' template.
*
* @param {HTMLElement} areaReplace the DOM node to replace.
* @returns {Promise}
*/
var renderNoCourses = function(areaReplace) {
return Templates.renderPix('courses', 'tool_moodlenet').then(function(img) {
return img;
}).then(function(img) {
var temp = document.createElement('div');
temp.innerHTML = img.trim();
return Templates.render('core_course/no-courses', {
nocoursesimg: temp.firstChild.src
});
}).then(function(html, js) {
Templates.replaceNodeContents(areaReplace, html, js);
areaReplace.classList.add('mx-auto');
areaReplace.classList.add('w-25');
return;
});
};
/**
* Render the course cards for those supplied courses.
*
* @param {HTMLElement} areaReplace the DOM node to replace.
* @param {Array<courses>} courses the courses to render.
* @returns {Promise}
*/
var renderCourses = function(areaReplace, courses) {
return Templates.render('tool_moodlenet/view-cards', {
courses: courses
}).then(function(html, js) {
Templates.replaceNodeContents(areaReplace, html, js);
areaReplace.classList.remove('mx-auto');
areaReplace.classList.remove('w-25');
return;
});
};
/**
* For a given input, the page & what to replace fetch courses and manage icons too.
*
* @method searchCourses
* @param {string} inputValue What to search for
* @param {HTMLElement} page The whole page element for our page
* @param {HTMLElement} areaReplace The Element to replace the contents of
*/
var searchCourses = function(inputValue, page, areaReplace) {
var searchIcon = page.querySelector(Selectors.region.searchIcon);
var clearIcon = page.querySelector(Selectors.region.clearIcon);
if (inputValue !== '') {
searchIcon.classList.add('d-none');
clearIcon.parentElement.classList.remove('d-none');
} else {
searchIcon.classList.remove('d-none');
clearIcon.parentElement.classList.add('d-none');
}
var args = {
searchvalue: inputValue,
};
Ajax.call([{
methodname: 'tool_moodlenet_search_courses',
args: args
}])[0].then(function(result) {
if (result.courses.length === 0) {
return renderNoCourses(areaReplace);
} else {
// Add the importId to the course link
result.courses.forEach(function(course) {
course.viewurl += '&id=' + importId;
});
return renderCourses(areaReplace, result.courses);
}
}).catch(Notification.exception);
};
/**
* Add the event listeners to our page.
*
* @method registerListenerEvents
* @param {HTMLElement} page The whole page element for our page
*/
var registerListenerEvents = function(page) {
var input = page.querySelector(Selectors.region.searchInput);
var courseArea = page.querySelector(Selectors.region.courses);
var clearIcon = page.querySelector(Selectors.region.clearIcon);
clearIcon.addEventListener('click', function() {
input.value = '';
searchCourses('', page, courseArea);
});
input.addEventListener('input', debounce(function() {
searchCourses(input.value, page, courseArea);
}, 300));
};
/**
* Fetch the courses to show the user. We use the same WS structure & template as the search for consistency.
*
* @method addCourses
* @param {HTMLElement} page The whole page element for our course page
*/
var addCourses = function(page) {
var courseArea = page.querySelector(Selectors.region.courses);
searchCourses('', page, courseArea);
};
/**
* Define our own debounce function as Moodle 3.7 does not have it.
*
* @method debounce
* @from underscore.js
* @copyright 2009-2020 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* @licence MIT
* @param {function} func The function we want to keep calling
* @param {number} wait Our timeout
* @param {boolean} immediate Do we want to apply the function immediately
* @return {function}
*/
var debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this;
var args = arguments;
var later = function() {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
};
return {
init: init,
};
});

View File

@ -0,0 +1,45 @@
// 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/>.
/**
* Define all of the selectors we will be using within MoodleNet plugin.
*
* @module tool_moodlenet/selectors
* @package tool_moodlenet
* @copyright 2020 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
return {
action: {
browse: '[data-action="browse"]',
submit: '[data-action="submit"]',
showMoodleNet: '[data-action="show-moodlenet"]',
closeOption: '[data-action="close-chooser-option-summary"]',
},
region: {
clearIcon: '[data-region="clear-icon"]',
courses: '[data-region="mnet-courses"]',
instancePage: '[data-region="moodle-net"]',
searchInput: '[data-region="search-input"]',
searchIcon: '[data-region="search-icon"]',
selectPage: '[data-region="moodle-net-select"]',
spinner: '[data-region="spinner"]',
validationArea: '[data-region="validation-area"]',
carousel: '[data-region="carousel"]',
moodleNet: '[data-region="pluginCarousel"]',
},
};
});

View File

@ -0,0 +1,59 @@
// 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/>.
/**
* Our validator that splits the user's input then fires off to a webservice
*
* @module tool_moodlenet/validator
* @package tool_moodlenet
* @copyright 2020 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/str', 'core/notification'], function($, Ajax, Str, Notification) {
/**
* Handle form validation
*
* @method validation
* @param {HTMLElement} inputElement The element the user entered text into.
* @return {Promise} Was the users' entry a valid profile URL?
*/
var validation = function validation(inputElement) {
var inputValue = inputElement.value;
// They didn't submit anything or they gave us a simple string that we can't do anything with.
if (inputValue === "" || !inputValue.includes("@")) {
// Create a promise and immediately reject it.
$.when(Str.get_string('profilevalidationerror', 'tool_moodlenet')).then(function(strings) {
return Promise.reject().catch(function() {
return {result: false, message: strings[0]};
});
}).fail(Notification.exception);
}
return Ajax.call([{
methodname: 'tool_moodlenet_verify_webfinger',
args: {
profileurl: inputValue,
course: inputElement.dataset.courseid,
section: inputElement.dataset.sectionid
}
}])[0].then(function(result) {
return result;
}).catch();
};
return {
validation: validation,
};
});

View File

@ -0,0 +1,189 @@
<?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/>.
/**
* This is the external API for this component.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir .'/externallib.php');
require_once($CFG->libdir . '/filelib.php');
require_once(__DIR__ . '/../lib.php');
use core_course\external\course_summary_exporter;
use external_api;
use external_function_parameters;
use external_value;
use external_single_structure;
/**
* This is the external API for this component.
*
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external extends external_api {
/**
* verify_webfinger parameters
*
* @return external_function_parameters
*/
public static function verify_webfinger_parameters() {
return new external_function_parameters(
array(
'profileurl' => new external_value(PARAM_RAW, 'The profile url that the user has given us', VALUE_REQUIRED),
'course' => new external_value(PARAM_INT, 'The course we are adding to', VALUE_REQUIRED),
'section' => new external_value(PARAM_INT, 'The section within the course we are adding to', VALUE_REQUIRED),
)
);
}
/**
* Figure out if the passed content resolves with a WebFinger account.
*
* @param string $profileurl The profile url that the user states exists
* @param int $course The course we are adding to
* @param int $section The section within the course we are adding to
* @return array Contains the result and domain if any
* @throws \invalid_parameter_exception
*/
public static function verify_webfinger(string $profileurl, int $course, int $section) {
global $USER;
$params = self::validate_parameters(self::verify_webfinger_parameters(), [
'profileurl' => $profileurl,
'section' => $section,
'course' => $course
]
);
try {
$mnetprofile = new moodlenet_user_profile($params['profileurl'], $USER->id);
} catch (\Exception $e) {
return [
'result' => false,
'message' => get_string('profilevalidationfail', 'tool_moodlenet'),
];
}
$userlink = profile_manager::get_moodlenet_profile_link($mnetprofile);
// There were no problems verifying the account so lets store it.
if ($userlink['result'] === true) {
profile_manager::save_moodlenet_user_profile($mnetprofile);
$userlink['domain'] = generate_mnet_endpoint($mnetprofile->get_profile_name(), $course, $section);
}
return $userlink;
}
/**
* verify_webfinger return.
*
* @return \external_description
*/
public static function verify_webfinger_returns() {
return new external_single_structure([
'result' => new external_value(PARAM_BOOL, 'Was the passed content a valid WebFinger?'),
'message' => new external_value(PARAM_TEXT, 'Our message for the user'),
'domain' => new external_value(PARAM_RAW, 'Domain to redirect the user to', VALUE_OPTIONAL),
]);
}
/**
* search_courses_parameters
*
* @return external_function_parameters
*/
public static function search_courses_parameters() {
return new external_function_parameters(
array(
'searchvalue' => new external_value(PARAM_RAW, 'search value'),
)
);
}
/**
* For some given input find and return any course that matches it.
*
* @param string $searchvalue The profile url that the user states exists
* @return array Contains the result set of courses for the value
*/
public static function search_courses(string $searchvalue) {
global $OUTPUT;
$params = self::validate_parameters(
self::search_courses_parameters(),
['searchvalue' => $searchvalue]
);
self::validate_context(\context_system::instance());
$courses = array();
if ($arrcourses = \core_course_category::search_courses(array('search' => $params['searchvalue']))) {
foreach ($arrcourses as $course) {
if (has_capability('moodle/course:manageactivities', \context_course::instance($course->id))) {
$data = new \stdClass();
$data->id = $course->id;
$data->fullname = $course->fullname;
$data->hidden = $course->visible;
$options = [
'course' => $course->id,
];
$viewurl = new \moodle_url('/admin/tool/moodlenet/options.php', $options);
$data->viewurl = $viewurl->out(false);
$category = \core_course_category::get($course->category);
$data->coursecategory = $category->name;
$courseimage = course_summary_exporter::get_course_image($data);
if (!$courseimage) {
$courseimage = $OUTPUT->get_generated_image_for_id($data->id);
}
$data->courseimage = $courseimage;
$courses[] = $data;
}
}
}
return array(
'courses' => $courses
);
}
/**
* search_courses_returns.
*
* @return \external_description
*/
public static function search_courses_returns() {
return new external_single_structure([
'courses' => new \external_multiple_structure(
new external_single_structure([
'id' => new external_value(PARAM_INT, 'course id'),
'fullname' => new external_value(PARAM_TEXT, 'course full name'),
'hidden' => new external_value(PARAM_INT, 'is the course visible'),
'viewurl' => new external_value(PARAM_URL, 'Next step of import'),
'coursecategory' => new external_value(PARAM_TEXT, 'Category name'),
'courseimage' => new external_value(PARAM_RAW, 'course image'),
]))
]);
}
}

View File

@ -0,0 +1,194 @@
<?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/>.
/**
* Contains the import_backup_helper class.
*
* @package tool_moodlenet
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_backup_helper class.
*
* The import_backup_helper objects provide a means to prepare a backup for for restoration of a course or activity backup file.
*
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_backup_helper {
/** @var remote_resource $remoteresource A file resource to be restored. */
protected $remoteresource;
/** @var user $user The user trying to restore a file. */
protected $user;
/** @var context $context The context we are trying to restore this file into. */
protected $context;
/** @var int $useruploadlimit The size limit that this user can upload in this context. */
protected $useruploadlimit;
/**
* Constructor for the import backup helper.
*
* @param remote_resource $remoteresource A remote file resource
* @param \stdClass $user The user importing a file.
* @param \context $context Context to restore into.
*/
public function __construct(remote_resource $remoteresource, \stdClass $user, \context $context) {
$this->remoteresource = $remoteresource;
$this->user = $user;
$this->context = $context;
$maxbytes = 0;
if ($this->context->contextlevel == CONTEXT_COURSE) {
$course = get_course($this->context->instanceid);
$maxbytes = $course->maxbytes;
}
$this->useruploadlimit = get_user_max_upload_file_size($this->context, get_config('core', 'maxbytes'),
$maxbytes, 0, $this->user);
}
/**
* Return a stored user draft file for processing.
*
* @return \stored_file The imported file to ultimately be restored.
*/
public function get_stored_file(): \stored_file {
// Check if the user can upload a backup to this context.
require_capability('moodle/restore:uploadfile', $this->context, $this->user->id);
// Before starting a potentially lengthy download, try to ensure the file size does not exceed the upload size restrictions
// for the user. This is a time saving measure.
// This is a naive check, that serves only to catch files if they provide the content length header.
// Because of potential content encoding (compression), the stored file will be checked again after download as well.
$size = $this->remoteresource->get_download_size() ?? -1;
if ($this->size_exceeds_upload_limit($size)) {
throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $size,
'uploadlimit' => $this->useruploadlimit]);
}
[$filepath, $filename] = $this->remoteresource->download_to_requestdir();
\core\antivirus\manager::scan_file($filepath, $filename, true);
// Check the final size of file against the user upload limits.
$localsize = filesize(sprintf('%s/%s', $filepath, $filename));
if ($this->size_exceeds_upload_limit($localsize)) {
throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $localsize,
'uploadlimit' => $this->useruploadlimit]);
}
return $this->create_user_draft_stored_file($filename, $filepath);
}
/**
* Does the size exceed the upload limit for the current import, taking into account user and core settings.
*
* @param int $sizeinbytes
* @return bool true if exceeded, false otherwise.
*/
protected function size_exceeds_upload_limit(int $sizeinbytes): bool {
$maxbytes = 0;
if ($this->context->contextlevel == CONTEXT_COURSE) {
$course = get_course($this->context->instanceid);
$maxbytes = $course->maxbytes;
}
$maxbytes = get_user_max_upload_file_size($this->context, get_config('core', 'maxbytes'), $maxbytes, 0,
$this->user);
if ($maxbytes != USER_CAN_IGNORE_FILE_SIZE_LIMITS && $sizeinbytes > $maxbytes) {
return true;
}
return false;
}
/**
* Create a file in the user drafts ready for use by plugins implementing dndupload_handle().
*
* @param string $filename the name of the file on disk
* @param string $path the path where the file is stored on disk
* @return \stored_file
*/
protected function create_user_draft_stored_file(string $filename, string $path): \stored_file {
global $CFG;
$record = new \stdClass();
$record->filearea = 'draft';
$record->component = 'user';
$record->filepath = '/';
$record->itemid = file_get_unused_draft_itemid();
$record->license = $CFG->sitedefaultlicense;
$record->author = '';
$record->filename = clean_param($filename, PARAM_FILE);
$record->contextid = \context_user::instance($this->user->id)->id;
$record->userid = $this->user->id;
$fullpathwithname = sprintf('%s/%s', $path, $filename);
$fs = get_file_storage();
return $fs->create_file_from_pathname($record, $fullpathwithname);
}
/**
* Looks for a context that this user has permission to upload backup files to.
* This gets a list of roles that the user has, checks for the restore:uploadfile capability and then sends back a context
* that has this permission if available.
*
* This starts with the highest context level and moves down i.e. system -> category -> course.
*
* @param int $userid The user ID that we are looking for a working context for.
* @return \context A context that allows the upload of backup files.
*/
public static function get_context_for_user(int $userid): ?\context {
global $DB;
if (is_siteadmin()) {
return \context_system::instance();
}
$sql = "SELECT ctx.id, ctx.contextlevel, ctx.instanceid, ctx.path, ctx.depth, ctx.locked
FROM {context} ctx
JOIN {role_assignments} r ON ctx.id = r.contextid
WHERE r.userid = :userid AND ctx.contextlevel IN (:contextsystem, :contextcategory, :contextcourse)
ORDER BY ctx.contextlevel ASC";
$params = [
'userid' => $userid,
'contextsystem' => CONTEXT_SYSTEM,
'contextcategory' => CONTEXT_COURSECAT,
'contextcourse' => CONTEXT_COURSE
];
$records = $DB->get_records_sql($sql, $params);
foreach ($records as $record) {
\context_helper::preload_from_record($record);
if ($record->contextlevel == CONTEXT_COURSECAT) {
$context = \context_coursecat::instance($record->instanceid);
} else if ($record->contextlevel == CONTEXT_COURSE) {
$context = \context_course::instance($record->instanceid);
} else {
$context = \context_system::instance();
}
if (has_capability('moodle/restore:uploadfile', $context, $userid)) {
return $context;
}
}
return null;
}
}

View File

@ -0,0 +1,91 @@
<?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/>.
/**
* Contains the import_handler_info class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_handler_info class.
*
* An import_handler_info object represent an resource import handler for a particular module.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_handler_info {
/** @var string $modulename the name of the module. */
protected $modulename;
/** @var string $description the description. */
protected $description;
/** @var import_strategy $importstrategy the strategy which will be used to import resources handled by this handler */
protected $importstrategy;
/**
* The import_handler_info constructor.
*
* @param string $modulename the name of the module handling the file extension. E.g. 'label'.
* @param string $description A description of how the module handles files of this extension type.
* @param import_strategy $strategy the strategy which will be used to import the resource.
* @throws \coding_exception
*/
public function __construct(string $modulename, string $description, import_strategy $strategy) {
if (empty($modulename)) {
throw new \coding_exception("Module name cannot be empty.");
}
if (empty($description)) {
throw new \coding_exception("Description cannot be empty.");
}
$this->modulename = $modulename;
$this->description = $description;
$this->importstrategy = $strategy;
}
/**
* Get the name of the module.
*
* @return string the module name, e.g. 'label'.
*/
public function get_module_name(): string {
return $this->modulename;
}
/**
* Get a human readable, localised description of how the file is handled by the module.
*
* @return string the localised description.
*/
public function get_description(): string {
return $this->description;
}
/**
* Get the import strategy used by this handler.
*
* @return import_strategy the import strategy object.
*/
public function get_strategy(): import_strategy {
return $this->importstrategy;
}
}

View File

@ -0,0 +1,188 @@
<?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/>.
/**
* Contains the import_handler_registry class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_handler_registry class.
*
* The import_handler_registry objects represent a register of modules handling various file extensions for a given course and user.
* Only modules which are available to the user in the course are included in the register for that user.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_handler_registry {
/**
* @var array array containing the names and messages of all modules handling import of resources as a 'file' type.
*/
protected $filehandlers = [];
/**
* @var array $typehandlers the array of modules registering as handlers of other, non-file types, indexed by typename.
*/
protected $typehandlers = [];
/**
* @var array $registry the aggregate of all registrations made by plugins, indexed by 'file' and 'type'.
*/
protected $registry = [];
/**
* @var \context_course the course context object.
*/
protected $context;
/**
* @var \stdClass a course object.
*/
protected $course;
/**
* @var \stdClass a user object.
*/
protected $user;
/**
* The import_handler_registry constructor.
*
* @param \stdClass $course the course, which impacts available handlers.
* @param \stdClass $user the user, which impacts available handlers.
*/
public function __construct(\stdClass $course, \stdClass $user) {
$this->course = $course;
$this->user = $user;
$this->context = \context_course::instance($course->id);
// Generate the full list of handlers for all extensions for this user and course.
$this->populate_handlers();
}
/**
* Get all handlers for the remote resource, depending on the strategy being used to import the resource.
*
* @param remote_resource $resource the remote resource.
* @param import_strategy $strategy an import_strategy instance.
* @return import_handler_info[] the array of import_handler_info handlers.
*/
public function get_resource_handlers_for_strategy(remote_resource $resource, import_strategy $strategy): array {
return $strategy->get_handlers($this->registry, $resource);
}
/**
* Get a specific handler for the resource, belonging to a specific module and for a specific strategy.
*
* @param remote_resource $resource the remote resource.
* @param string $modname the name of the module, e.g. 'label'.
* @param import_strategy $strategy a string representing how to treat the resource. e.g. 'file', 'link'.
* @return import_handler_info|null the import_handler_info object, if found, otherwise null.
*/
public function get_resource_handler_for_mod_and_strategy(remote_resource $resource, string $modname,
import_strategy $strategy): ?import_handler_info {
foreach ($strategy->get_handlers($this->registry, $resource) as $handler) {
if ($handler->get_module_name() === $modname) {
return $handler;
}
}
return null;
}
/**
* Build up a list of extension handlers by leveraging the dndupload_register callbacks.
*/
protected function populate_handlers() {
// Generate a dndupload_handler object, just so we can call ->is_known_type() on the types being registered by plugins.
// We must vet each type which is reported to be handled against the list of known, supported types.
global $CFG;
require_once($CFG->dirroot . '/course/dnduploadlib.php');
$dndhandlers = new \dndupload_handler($this->course);
// Get the list of mods enabled at site level first. We need to cross check this.
$pluginman = \core_plugin_manager::instance();
$sitemods = $pluginman->get_plugins_of_type('mod');
$sitedisabledmods = array_filter($sitemods, function(\core\plugininfo\mod $modplugininfo){
return !$modplugininfo->is_enabled();
});
$sitedisabledmods = array_map(function($modplugininfo) {
return $modplugininfo->name;
}, $sitedisabledmods);
// Loop through all modules to find the registered handlers.
$mods = get_plugin_list_with_function('mod', 'dndupload_register');
foreach ($mods as $component => $funcname) {
list($modtype, $modname) = \core_component::normalize_component($component);
if (!empty($sitedisabledmods) && array_key_exists($modname, $sitedisabledmods)) {
continue; // Module is disabled at the site level.
}
if (!course_allowed_module($this->course, $modname, $this->user)) {
continue; // User does not have permission to add this module to the course.
}
if (!$resp = component_callback($component, 'dndupload_register')) {
continue;
};
if (isset($resp['files'])) {
foreach ($resp['files'] as $file) {
$this->register_file_handler($file['extension'], $modname, $file['message']);
}
}
if (isset($resp['types'])) {
foreach ($resp['types'] as $type) {
if (!$dndhandlers->is_known_type($type['identifier'])) {
throw new \coding_exception("Trying to add handler for unknown type $type");
}
$this->register_type_handler($type['identifier'], $modname, $type['message']);
}
}
}
$this->registry = [
'files' => $this->filehandlers,
'types' => $this->typehandlers
];
}
/**
* Adds a type handler to the list.
*
* @param string $identifier the name of the type.
* @param string $module the name of the module, e.g. 'label'.
* @param string $message the message describing how the module handles the type.
*/
protected function register_type_handler(string $identifier, string $module, string $message) {
$this->typehandlers[$identifier][] = ['module' => $module, 'message' => $message];
}
/**
* Adds a file extension handler to the list.
*
* @param string $extension the extension, e.g. 'png'.
* @param string $module the name of the module handling this extension
* @param string $message the message describing how the module handles the extension.
*/
protected function register_file_handler(string $extension, string $module, string $message) {
$extension = strtolower($extension);
$this->filehandlers[$extension][] = ['module' => $module, 'message' => $message];
}
}

View File

@ -0,0 +1,126 @@
<?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/>.
/**
* Contains the import_info class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* Class import_info, describing objects which represent a resource being imported by a user.
*
* Objects of this class encapsulate both:
* - information about the resource (remote_resource).
* - config data pertaining to the import process, such as the destination course and section
* and how the resource should be treated (i.e. the type and the name of the module selected as the import handler)
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_info {
/** @var int $userid the user conducting this import. */
protected $userid;
/** @var remote_resource $resource the resource being imported. */
protected $resource;
/** @var \stdClass $config config data pertaining to the import process, e.g. course, section, type. */
protected $config;
/** @var string $id string identifier for this object. */
protected $id;
/**
* The import_controller constructor.
*
* @param int $userid the id of the user performing the import.
* @param remote_resource $resource the resource being imported.
* @param \stdClass $config import config like 'course', 'section', 'type'.
*/
public function __construct(int $userid, remote_resource $resource, \stdClass $config) {
$this->userid = $userid;
$this->resource = $resource;
$this->config = $config;
$this->id = md5($resource->get_url()->get_value());
}
/**
* Get the id of this object.
*/
public function get_id() {
return $this->id;
}
/**
* Get the remote resource being imported.
*
* @return remote_resource the remote resource being imported.
*/
public function get_resource(): remote_resource {
return $this->resource;
}
/**
* Get the configuration data pertaining to the import.
*
* @return \stdClass the import configuration data.
*/
public function get_config(): \stdClass {
return $this->config;
}
/**
* Set the configuration data pertaining to the import.
*
* @param \stdClass $config the configuration data to set.
*/
public function set_config(\stdClass $config): void {
$this->config = $config;
}
/**
* Get an import_info object by id.
*
* @param string $id the id of the import_info object to load.
* @return mixed an import_info object if found, otherwise null.
*/
public static function load(string $id): ?import_info {
// This currently lives in the session, so we don't need userid.
// It might be useful if we ever move to another storage mechanism however, where we would need it.
global $SESSION;
return isset($SESSION->moodlenetimports[$id]) ? unserialize($SESSION->moodlenetimports[$id]) : null;
}
/**
* Save this object to a store which is accessible across requests.
*/
public function save(): void {
global $SESSION;
$SESSION->moodlenetimports[$this->id] = serialize($this);
}
/**
* Remove all information about an import from the store.
*/
public function purge(): void {
global $SESSION;
unset($SESSION->moodlenetimports[$this->id]);
}
}

View File

@ -0,0 +1,206 @@
<?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/>.
/**
* Contains the import_processor class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_processor class.
*
* The import_processor objects provide a means to import a remote resource into a course section, delegating the handling of
* content to the relevant module, via its dndupload_handler callback.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_processor {
/** @var object The course that we are uploading to */
protected $course = null;
/** @var int The section number we are uploading to */
protected $section = null;
/** @var import_handler_registry $handlerregistry registry object to use for cross checking the supplied handler.*/
protected $handlerregistry;
/** @var import_handler_info $handlerinfo information about the module handling the import.*/
protected $handlerinfo;
/** @var \stdClass $user the user conducting the import.*/
protected $user;
/** @var remote_resource $remoteresource the remote resource being imported.*/
protected $remoteresource;
/** @var string[] $descriptionoverrides list of modules which support having their descriptions updated, post-import. */
protected $descriptionoverrides = ['folder', 'page', 'resource', 'scorm', 'url'];
/**
* The import_processor constructor.
*
* @param \stdClass $course the course object.
* @param int $section the section number in the course, starting at 0.
* @param remote_resource $remoteresource the remote resource to import.
* @param import_handler_info $handlerinfo information about which module is handling the import.
* @param import_handler_registry $handlerregistry A registry of import handlers, to use for validation.
* @throws \coding_exception If any of the params are invalid.
*/
public function __construct(\stdClass $course, int $section, remote_resource $remoteresource, import_handler_info $handlerinfo,
import_handler_registry $handlerregistry) {
global $DB, $USER;
if ($section < 0) {
throw new \coding_exception("Invalid section number $section. Must be > 0.");
}
if (!$DB->record_exists('modules', array('name' => $handlerinfo->get_module_name()))) {
throw new \coding_exception("Module {$handlerinfo->get_module_name()} does not exist");
}
$this->course = $course;
$this->section = $section;
$this->handlerregistry = $handlerregistry;
$this->user = $USER;
$this->remoteresource = $remoteresource;
$this->handlerinfo = $handlerinfo;
// ALL handlers must have a strategy and ANY strategy can process ANY resource.
// It is therefore NOT POSSIBLE to have a resource that CANNOT be processed by a handler.
// So, there's no need to verify that the remote_resource CAN be handled by the handler. It always can.
}
/**
* Run the import process, including file download, module creation and cleanup (cache purge, etc).
*/
public function process(): void {
// Allow the strategy to do setup for this file import.
$moduledata = $this->handlerinfo->get_strategy()->import($this->remoteresource, $this->user, $this->course, $this->section);
// Create the course module, and add that information to the data to be sent to the plugin handling the resource.
$cmdata = $this->create_course_module($this->course, $this->section, $this->handlerinfo->get_module_name());
$moduledata->coursemodule = $cmdata->id;
// Now, send the data to the handling plugin to let it set up.
$instanceid = plugin_callback('mod', $this->handlerinfo->get_module_name(), 'dndupload', 'handle', [$moduledata],
'invalidfunction');
if ($instanceid == 'invalidfunction') {
$name = $this->handlerinfo->get_module_name();
throw new \coding_exception("$name does not support drag and drop upload (missing {$name}_dndupload_handle function)");
}
// Now, update the module description if the module supports it and only if it's not currently set.
$this->update_module_description($instanceid);
// Finish setting up the course module.
$this->finish_setup_course_module($instanceid, $cmdata->id);
}
/**
* Update the module's description (intro), if that feature is supported.
*
* @param int $instanceid the instance id of the module to update.
*/
protected function update_module_description(int $instanceid): void {
global $DB, $CFG;
require_once($CFG->libdir . '/moodlelib.php');
if (plugin_supports('mod', $this->handlerinfo->get_module_name(), FEATURE_MOD_INTRO, true)) {
require_once($CFG->libdir . '/editorlib.php');
require_once($CFG->libdir . '/modinfolib.php');
$rec = $DB->get_record($this->handlerinfo->get_module_name(), ['id' => $instanceid]);
if (empty($rec->intro) || in_array($this->handlerinfo->get_module_name(), $this->descriptionoverrides)) {
$updatedata = (object)[
'id' => $instanceid,
'intro' => clean_param($this->remoteresource->get_description(), PARAM_TEXT),
'introformat' => editors_get_preferred_format()
];
$DB->update_record($this->handlerinfo->get_module_name(), $updatedata);
rebuild_course_cache($this->course->id, true);
}
}
}
/**
* Create the course module to hold the file/content that has been uploaded.
* @param \stdClass $course the course object.
* @param int $section the section.
* @param string $modname the name of the module, e.g. 'label'.
* @return \stdClass the course module data.
*/
protected function create_course_module(\stdClass $course, int $section, string $modname): \stdClass {
global $CFG;
require_once($CFG->dirroot . '/course/modlib.php');
list($module, $context, $cw, $cm, $data) = prepare_new_moduleinfo_data($course, $modname, $section);
$data->visible = false; // The module is created in a hidden state.
$data->coursemodule = $data->id = add_course_module($data);
return $data;
}
/**
* Finish off any course module setup, such as adding to the course section and firing events.
*
* @param int $instanceid id returned by the mod when it was created.
* @param int $cmid the course module record id, for removal if something went wrong.
*/
protected function finish_setup_course_module($instanceid, int $cmid): void {
global $DB;
if (!$instanceid) {
// Something has gone wrong - undo everything we can.
course_delete_module($cmid);
throw new \moodle_exception('errorcreatingactivity', 'moodle', '', $this->handlerinfo->get_module_name());
}
// Note the section visibility.
$visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible;
$DB->set_field('course_modules', 'instance', $instanceid, array('id' => $cmid));
// Rebuild the course cache after update action.
rebuild_course_cache($this->course->id, true);
course_add_cm_to_section($this->course, $cmid, $this->section);
set_coursemodule_visible($cmid, $visible);
if (!$visible) {
$DB->set_field('course_modules', 'visibleold', 1, array('id' => $cmid));
}
// Retrieve the final info about this module.
$info = get_fast_modinfo($this->course, $this->user->id);
if (!isset($info->cms[$cmid])) {
// The course module has not been properly created in the course - undo everything.
course_delete_module($cmid);
throw new \moodle_exception('errorcreatingactivity', 'moodle', '', $this->handlerinfo->get_module_name());
}
$mod = $info->get_cm($cmid);
// Trigger course module created event.
$event = \core\event\course_module_created::create_from_cm($mod);
$event->trigger();
}
}

View File

@ -0,0 +1,69 @@
<?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/>.
/**
* Contains the import_strategy interface.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_strategy interface.
*
* This provides a contract allowing different import strategies to be implemented.
*
* An import_strategy encapsulates the logic used to prepare a remote_resource for import into Moodle in some way and is used by the
* import_processor (to perform aforementioned preparations) before it hands control of the import over to a course module plugin.
*
* We may wish to have many strategies because the preparation steps may vary depending on how the resource is to be treated.
* E.g. We may wish to import as a file in which case download steps will be required, or we may simply wish to import the remote
* resource as a link, in which cases setup steps will not require any file download.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface import_strategy {
/**
* Get an array of import_handler_info objects supported by this import strategy, based on the registrydata and resource.
*
* Implementations should check the registry data for any entries which align with their import strategy and should create
* import_handler_info objects to represent each relevant entry. If an entry represents a module, or handling type which does
* not align with the strategy, that item should simply be skipped.
*
* E.g. If one strategy aims to import all remote resources as files (e.g. import_strategy_file), it would only generate a list
* of import_handler_info objects created from those registry entries of type 'file', as those entries represent the modules
* which have said they can handle resources as files.
*
* @param array $registrydata The fully populated handler registry.
* @param remote_resource $resource the remote resource.
* @return import_handler_info[] the array of import_handler_info objects, or an empty array if none were matched.
*/
public function get_handlers(array $registrydata, remote_resource $resource): array;
/**
* Called during import to perform required import setup steps.
*
* @param remote_resource $resource the resource to import.
* @param \stdClass $user the user to import on behalf of.
* @param \stdClass $course the course into which the remote resource is being imported.
* @param int $section the section into which the remote resource is being imported.
* @return \stdClass the module data which will be passed on to the course module plugin.
*/
public function import(remote_resource $resource, \stdClass $user, \stdClass $course, int $section): \stdClass;
}

View File

@ -0,0 +1,170 @@
<?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/>.
/**
* Contains the import_strategy_file class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
use core\antivirus\manager as avmanager;
/**
* The import_strategy_file class.
*
* The import_strategy_file objects contains the setup steps needed to prepare a resource for import as a file into Moodle. This
* ensures the remote_resource is first downloaded and put in a draft file area, ready for use as a file by the handling module.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_strategy_file implements import_strategy {
/**
* Get an array of import_handler_info objects representing modules supporting import of this file type.
*
* @param array $registrydata the fully populated registry.
* @param remote_resource $resource the remote resource.
* @return import_handler_info[] the array of import_handler_info objects.
*/
public function get_handlers(array $registrydata, remote_resource $resource): array {
$handlers = [];
foreach ($registrydata['files'] as $index => $items) {
foreach ($items as $item) {
if ($index === $resource->get_extension() || $index === '*') {
$handlers[] = new import_handler_info($item['module'], $item['message'], $this);
}
}
}
return $handlers;
}
/**
* Import the remote resource according to the rules of this strategy.
*
* @param remote_resource $resource the resource to import.
* @param \stdClass $user the user to import on behalf of.
* @param \stdClass $course the course into which the remote_resource is being imported.
* @param int $section the section into which the remote_resource is being imported.
* @return \stdClass the module data.
* @throws \moodle_exception if the file size means the upload limit is exceeded for the user.
*/
public function import(remote_resource $resource, \stdClass $user, \stdClass $course, int $section): \stdClass {
// Before starting a potentially lengthy download, try to ensure the file size does not exceed the upload size restrictions
// for the user. This is a time saving measure.
// This is a naive check, that serves only to catch files if they provide the content length header.
// Because of potential content encoding (compression), the stored file will be checked again after download as well.
$size = $resource->get_download_size() ?? -1;
$useruploadlimit = $this->get_user_upload_limit($user, $course);
if ($this->size_exceeds_upload_limit($size, $useruploadlimit)) {
throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $size,
'uploadlimit' => $useruploadlimit]);
}
// Download the file into a request directory and scan it.
[$filepath, $filename] = $resource->download_to_requestdir();
avmanager::scan_file($filepath, $filename, true);
// Check the final size of file against the user upload limits.
$localsize = filesize(sprintf('%s/%s', $filepath, $filename));
if ($this->size_exceeds_upload_limit($localsize, $useruploadlimit)) {
throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $localsize,
'uploadlimit' => $useruploadlimit]);
}
// Store in the user draft file area.
$storedfile = $this->create_user_draft_stored_file($user, $filename, $filepath);
// Prepare the data to be sent to the modules dndupload_handle hook.
return $this->prepare_module_data($course, $resource, $storedfile->get_itemid());
}
/**
* Creates the data to pass to the dndupload_handle() hooks.
*
* @param \stdClass $course the course record.
* @param remote_resource $resource the resource being imported as a file.
* @param int $draftitemid the itemid of the draft file.
* @return \stdClass the data object.
*/
protected function prepare_module_data(\stdClass $course, remote_resource $resource, int $draftitemid): \stdClass {
$data = new \stdClass();
$data->type = 'Files';
$data->course = $course;
$data->draftitemid = $draftitemid;
$data->displayname = $resource->get_name();
return $data;
}
/**
* Get the max file size limit for the user in the course.
*
* @param \stdClass $user the user to check.
* @param \stdClass $course the course to check in.
* @return int the file size limit, in bytes.
*/
protected function get_user_upload_limit(\stdClass $user, \stdClass $course): int {
return get_user_max_upload_file_size(\context_course::instance($course->id), get_config('core', 'maxbytes'),
$course->maxbytes, 0, $user);
}
/**
* Does the size exceed the upload limit for the current import, taking into account user and core settings.
*
* @param int $sizeinbytes the size, in bytes.
* @param int $useruploadlimit the upload limit, in bytes.
* @return bool true if exceeded, false otherwise.
* @throws \dml_exception
*/
protected function size_exceeds_upload_limit(int $sizeinbytes, int $useruploadlimit): bool {
if ($useruploadlimit != USER_CAN_IGNORE_FILE_SIZE_LIMITS && $sizeinbytes > $useruploadlimit) {
return true;
}
return false;
}
/**
* Create a file in the user drafts ready for use by plugins implementing dndupload_handle().
*
* @param \stdClass $user the user object.
* @param string $filename the name of the file on disk
* @param string $path the path where the file is stored on disk
* @return \stored_file
*/
protected function create_user_draft_stored_file(\stdClass $user, string $filename, string $path): \stored_file {
global $CFG;
$record = new \stdClass();
$record->filearea = 'draft';
$record->component = 'user';
$record->filepath = '/';
$record->itemid = file_get_unused_draft_itemid();
$record->license = $CFG->sitedefaultlicense;
$record->author = '';
$record->filename = clean_param($filename, PARAM_FILE);
$record->contextid = \context_user::instance($user->id)->id;
$record->userid = $user->id;
$fullpathwithname = sprintf('%s/%s', $path, $filename);
$fs = get_file_storage();
return $fs->create_file_from_pathname($record, $fullpathwithname);
}
}

View File

@ -0,0 +1,71 @@
<?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/>.
/**
* Contains the import_strategy_link class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_strategy_link class.
*
* The import_strategy_link objects contains the setup steps needed to prepare a resource for import as a URL into Moodle.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_strategy_link implements import_strategy {
/**
* Get an array of import_handler_info objects representing modules supporting import of the resource.
*
* @param array $registrydata the fully populated registry.
* @param remote_resource $resource the remote resource.
* @return import_handler_info[] the array of import_handler_info objects.
*/
public function get_handlers(array $registrydata, remote_resource $resource): array {
$handlers = [];
foreach ($registrydata['types'] as $identifier => $items) {
foreach ($items as $item) {
if ($identifier === 'url') {
$handlers[] = new import_handler_info($item['module'], $item['message'], $this);
}
}
}
return $handlers;
}
/**
* Import the remote resource according to the rules of this strategy.
*
* @param remote_resource $resource the resource to import.
* @param \stdClass $user the user to import on behalf of.
* @param \stdClass $course the course into which the remote_resource is being imported.
* @param int $section the section into which the remote_resource is being imported.
* @return \stdClass the module data.
*/
public function import(remote_resource $resource, \stdClass $user, \stdClass $course, int $section): \stdClass {
$data = new \stdClass();
$data->type = 'url';
$data->course = $course;
$data->content = $resource->get_url()->get_value();
$data->displayname = $resource->get_name();
return $data;
}
}

View File

@ -0,0 +1,169 @@
<?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/>.
/**
* Contains the remote_resource class definition.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The remote_resource class.
*
* Objects of type remote_resource provide a means of interacting with resources over HTTP.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class remote_resource {
/** @var \curl $curl the curl http helper.*/
protected $curl;
/** @var url $url the url to the remote resource.*/
protected $url;
/** @var string $filename the name of this remote file.*/
protected $filename;
/** @var string $extension the file extension of this remote file.*/
protected $extension;
/** @var array $headinfo the array of information for the most recent HEAD request.*/
protected $headinfo = [];
/** @var \stdClass $metadata information about the resource. */
protected $metadata;
/**
* The remote_resource constructor.
*
* @param \curl $curl a curl object for HTTP requests.
* @param url $url the URL of the remote resource.
* @param \stdClass $metadata resource metadata such as name, summary, license, etc.
*/
public function __construct(\curl $curl, url $url, \stdClass $metadata) {
$this->curl = $curl;
$this->url = $url;
$this->filename = pathinfo($this->url->get_path(), PATHINFO_FILENAME);
$this->extension = pathinfo($this->url->get_path(), PATHINFO_EXTENSION);
$this->metadata = $metadata;
}
/**
* Return the URL for this remote resource.
*
* @return url the url object.
*/
public function get_url(): url {
return $this->url;
}
/**
* Get the name of the file as taken from the metadata.
*/
public function get_name(): string {
return $this->metadata->name ?? '';
}
/**
* Get the resource metadata.
*
* @return \stdClass the metadata.
*/
public function get_metadata(): \stdClass {
return$this->metadata;
}
/**
* Get the description of the resource as taken from the metadata.
*
* @return string
*/
public function get_description(): string {
return $this->metadata->description ?? '';
}
/**
* Return the extension of the file, if found.
*
* @return string the extension of the file, if found.
*/
public function get_extension(): string {
return $this->extension;
}
/**
* Returns the file size of the remote file, in bytes, or null if it cannot be determined.
*
* @return int|null the content length, if able to be determined, otherwise null.
*/
public function get_download_size(): ?int {
$this->get_resource_info();
return $this->headinfo['download_content_length'] ?? null;
}
/**
* Download the remote resource to a local requestdir, returning the path and name of the resulting file.
*
* @return array an array containing filepath adn filename, e.g. [filepath, filename].
* @throws \moodle_exception if the file cannot be downloaded.
*/
public function download_to_requestdir(): array {
$filename = sprintf('%s.%s', $this->filename, $this->get_extension());
$path = make_request_directory();
$fullpathwithname = sprintf('%s/%s', $path, $filename);
// In future, use a timeout (download and/or connection) controlled by a tool_moodlenet setting.
$downloadtimeout = 30;
$result = $this->curl->download_one($this->url->get_value(), null, ['filepath' => $fullpathwithname,
'timeout' => $downloadtimeout]);
if ($result !== true) {
throw new \moodle_exception('errorduringdownload', 'tool_moodlenet', '', $result);
}
return [$path, $filename];
}
/**
* Fetches information about the remote resource via a HEAD request.
*
* @throws \coding_exception if any connection problems occur.
*/
protected function get_resource_info() {
if (!empty($this->headinfo)) {
return;
}
$options['CURLOPT_RETURNTRANSFER'] = 1;
$options['CURLOPT_FOLLOWLOCATION'] = 1;
$options['CURLOPT_MAXREDIRS'] = 5;
$options['CURLOPT_FAILONERROR'] = 1; // We want to consider http error codes as errors to report, not just status codes.
$this->curl->head($this->url->get_value(), $options);
$errorno = $this->curl->get_errno();
$this->curl->resetopt();
if ($errorno !== 0) {
$message = 'Problem during HEAD request for remote resource \''.$this->url->get_value().'\'. Curl Errno: ' . $errorno;
throw new \coding_exception($message);
}
$this->headinfo = $this->curl->get_info();
}
}

View File

@ -0,0 +1,84 @@
<?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/>.
/**
* Contains the url class, providing a representation of a url and operations on its component parts.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The url class, providing a representation of a url and operations on its component parts.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class url {
/** @var string $url the full URL string.*/
protected $url;
/** @var string|null $path the path component of this URL.*/
protected $path;
/** @var host|null $host the host component of this URL.*/
protected $host;
/**
* The url constructor.
*
* @param string $url the URL string.
* @throws \coding_exception if the URL does not pass syntax validation.
*/
public function __construct(string $url) {
// This object supports URLs as per the spec, so non-ascii chars must be encoded as per IDNA rules.
if (!filter_var($url, FILTER_VALIDATE_URL)) {
throw new \coding_exception('Malformed URL');
}
$this->url = $url;
$this->path = parse_url($url, PHP_URL_PATH);
$this->host = parse_url($url, PHP_URL_HOST);
}
/**
* Get the path component of the URL.
*
* @return string|null the path component of the URL.
*/
public function get_path(): ?string {
return $this->path;
}
/**
* Return the domain component of the URL.
*
* @return string|null the domain component of the URL.
*/
public function get_host(): ?string {
return $this->host;
}
/**
* Return the full URL string.
*
* @return string the full URL string.
*/
public function get_value() {
return $this->url;
}
}

View File

@ -0,0 +1,107 @@
<?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/>.
/**
* Moodle net user profile class.
*
* @package tool_moodlenet
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet;
/**
* A class to represent the moodlenet profile.
*
* @package tool_moodlenet
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_user_profile {
/** @var string $profile The full profile name. */
protected $profile;
/** @var int $userid The user ID that this profile belongs to. */
protected $userid;
/** @var string $username The username from $userprofile */
protected $username;
/** @var string $domain The domain from $domain */
protected $domain;
/**
* Constructor method.
*
* @param string $userprofile The moodle net user profile string.
* @param int $userid The user ID that this profile belongs to.
*/
public function __construct(string $userprofile, int $userid) {
$this->profile = $userprofile;
$this->userid = $userid;
$explodedprofile = explode('@', $this->profile);
if (count($explodedprofile) === 2) {
// It'll either be an email or WebFinger entry.
$this->username = $explodedprofile[0];
$this->domain = $explodedprofile[1];
} else if (count($explodedprofile) === 3) {
// We may have a profile link as MoodleNet gives to the user.
$this->username = $explodedprofile[1];
$this->domain = $explodedprofile[2];
} else {
throw new \moodle_exception('invalidmoodlenetprofile', 'tool_moodlenet');
}
}
/**
* Get the full moodle net profile.
*
* @return string The moodle net profile.
*/
public function get_profile_name(): string {
return $this->profile;
}
/**
* Get the user ID that this profile belongs to.
*
* @return int The user ID.
*/
public function get_userid(): int {
return $this->userid;
}
/**
* Get the username for this profile.
*
* @return string The username.
*/
public function get_username(): string {
return $this->username;
}
/**
* Get the domain for this profile.
*
* @return string The domain.
*/
public function get_domain(): string {
return $this->domain;
}
}

View File

@ -0,0 +1,52 @@
<?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/>.
/**
* Renderer.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\output;
defined('MOODLE_INTERNAL') || die();
use plugin_renderer_base;
/**
* Renderer class.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Defer to template.
*
* @param select_page $selectpage
* @return string HTML
*/
protected function render_select_page(select_page $selectpage): string {
$this->page->requires->js_call_amd('tool_moodlenet/select_page', 'init', [$selectpage->get_import_info()->get_id()]);
$data = $selectpage->export_for_template($this);
return parent::render_from_template('tool_moodlenet/select_page', $data);
}
}

View File

@ -0,0 +1,76 @@
<?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/>.
/**
* Select page renderable.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\output;
defined('MOODLE_INTERNAL') || die;
use tool_moodlenet\local\import_info;
/**
* Select page renderable.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class select_page implements \renderable, \templatable {
/** @var import_info $importinfo resource and config information pertaining to an import. */
protected $importinfo;
/**
* Inits the Select page renderable.
*
* @param import_info $importinfo resource and config information pertaining to an import.
*/
public function __construct(import_info $importinfo) {
$this->importinfo = $importinfo;
}
/**
* Return the import info.
*
* @return import_info the import information.
*/
public function get_import_info(): import_info {
return $this->importinfo;
}
/**
* Export the data.
*
* @param \renderer_base $output
* @return \stdClass
*/
public function export_for_template(\renderer_base $output): \stdClass {
// Prepare the context object.
return (object) [
'name' => $this->importinfo->get_resource()->get_name(),
'type' => $this->importinfo->get_config()->type,
'cancellink' => new \moodle_url('/my'),
];
}
}

View File

@ -0,0 +1,44 @@
<?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/>.
/**
* Privacy class for tool_moodlenet.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy class for tool_moodlenet.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason() : string {
return 'privacy:metadata';
}
}

View File

@ -0,0 +1,350 @@
<?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/>.
/**
* Profile manager class
*
* @package tool_moodlenet
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet;
/**
* Class for handling interaction with the moodlenet profile.
*
* @package tool_moodlenet
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class profile_manager {
/**
* Get the mnet profile for a user.
*
* @param int $userid The ID for the user to get the profile form
* @return moodlenet_user_profile or null.
*/
public static function get_moodlenet_user_profile(int $userid): ?moodlenet_user_profile {
global $CFG;
// Check for official profile.
if (self::official_profile_exists()) {
$user = \core_user::get_user($userid, 'moodlenetprofile');
try {
$userprofile = $user->moodlenetprofile ? $user->moodlenetprofile : '';
return (isset($user)) ? new moodlenet_user_profile($userprofile, $userid) : null;
} catch (\moodle_exception $e) {
// If an exception is thrown, means there isn't a valid profile set. No need to log exception.
return null;
}
}
// Otherwise get hacked in user profile field.
require_once($CFG->dirroot . '/user/profile/lib.php');
$profilefields = profile_get_user_fields_with_data($userid);
foreach ($profilefields as $key => $field) {
if ($field->get_category_name() == self::get_category_name()
&& $field->inputname == 'profile_field_mnetprofile') {
try {
return new moodlenet_user_profile($field->display_data(), $userid);
} catch (\moodle_exception $e) {
// If an exception is thrown, means there isn't a valid profile set. No need to log exception.
return null;
}
}
}
return null;
}
/**
* Save the moodlenet profile.
*
* @param moodlenet_user_profile $moodlenetprofile The moodlenet profile to save.
*/
public static function save_moodlenet_user_profile(moodlenet_user_profile $moodlenetprofile): void {
global $CFG, $DB;
// Do some cursory checks first to see if saving is possible.
if (self::official_profile_exists()) {
// All good. Let's save.
$user = \core_user::get_user($moodlenetprofile->get_userid());
$user->moodlenetprofile = $moodlenetprofile->get_profile_name();
require_once($CFG->dirroot . '/user/lib.php');
\user_update_user($user, false, true);
return;
}
$fielddata = self::get_user_profile_field();
$fielddata = self::validate_and_fix_missing_profile_items($fielddata);
// Everything should be back to normal. Let's save.
require_once($CFG->dirroot . '/user/profile/lib.php');
\profile_save_custom_fields($moodlenetprofile->get_userid(),
[$fielddata->shortname => $moodlenetprofile->get_profile_name()]);
}
/**
* Checks to see if the required user profile fields and categories are in place. If not it regenerates them.
*
* @param stdClass $fielddata The moodlenet profile field.
* @return stdClass The same moodlenet profile field, with any necessary updates made.
*/
private static function validate_and_fix_missing_profile_items(\stdClass $fielddata): \stdClass {
global $DB;
if (empty((array) $fielddata)) {
// We need to regenerate the category and field to store this data.
if (!self::check_profile_category()) {
$categoryid = self::create_user_profile_category();
self::create_user_profile_text_field($categoryid);
} else {
// We need the category id.
$category = $DB->get_record('user_info_category', ['name' => self::get_category_name()]);
self::create_user_profile_text_field($category->id);
}
$fielddata = self::get_user_profile_field();
} else {
if (!self::check_profile_category($fielddata->categoryid)) {
$categoryid = self::create_user_profile_category();
// Update the field to put it back into this category.
$fielddata->categoryid = $categoryid;
$DB->update_record('user_info_field', $fielddata);
}
}
return $fielddata;
}
/**
* Returns the user profile field table object.
*
* @return stdClass the moodlenet profile table object. False if no record found.
*/
private static function get_user_profile_field(): \stdClass {
global $DB;
$fieldname = self::get_profile_field_name();
$record = $DB->get_record('user_info_field', ['shortname' => $fieldname]);
return ($record) ? $record : (object) [];
}
/**
* This reports back if the category has been deleted or the config value is different.
*
* @param int $categoryid The category id to check against.
* @return bool True is the category checks out, otherwise false.
*/
private static function check_profile_category(int $categoryid = null): bool {
global $DB;
$categoryname = self::get_category_name();
$categorydata = $DB->get_record('user_info_category', ['name' => $categoryname]);
if (empty($categorydata)) {
return false;
}
if (isset($categoryid) && $categorydata->id != $categoryid) {
return false;
}
return true;
}
/**
* Are we using the proper user profile field to hold the mnet profile?
*
* @return bool True if we are using a user table field for the mnet profile. False means we are using costom profile fields.
*/
public static function official_profile_exists(): bool {
global $DB;
$usertablecolumns = $DB->get_columns('user', false);
if (isset($usertablecolumns['moodlenetprofile'])) {
return true;
}
return false;
}
/**
* Gets the category name that is set for this site.
*
* @return string The category used to hold the moodle net profile field.
*/
public static function get_category_name(): string {
return get_config('tool_moodlenet', 'profile_category');
}
/**
* Sets the a unique category to hold the moodle net user profile.
*
* @param string $categoryname The base category name to use.
* @return string The actual name of the category to use.
*/
private static function set_category_name(string $categoryname): string {
global $DB;
$attemptname = $categoryname;
// Check if this category already exists.
$foundcategoryname = false;
$i = 0;
do {
$category = $DB->count_records('user_info_category', ['name' => $attemptname]);
if ($category > 0) {
$i++;
$attemptname = $categoryname . $i;
} else {
set_config('profile_category', $attemptname, 'tool_moodlenet');
$foundcategoryname = true;
}
} while (!$foundcategoryname);
return $attemptname;
}
/**
* Create a custom user profile category to hold our custom field.
*
* @return int The id of the created category.
*/
public static function create_user_profile_category(): int {
global $DB;
// No nice API to do this, so direct DB calls it is.
$data = new \stdClass();
$data->sortorder = $DB->count_records('user_info_category') + 1;
$data->name = self::set_category_name(get_string('pluginname', 'tool_moodlenet'));
$data->id = $DB->insert_record('user_info_category', $data, true);
$createdcategory = $DB->get_record('user_info_category', array('id' => $data->id));
\core\event\user_info_category_created::create_from_category($createdcategory)->trigger();
return $createdcategory->id;
}
/**
* Sets a unique name to be used for the moodle net profile.
*
* @param string $fieldname The base fieldname to use.
* @return string The actual profile field name.
*/
private static function set_profile_field_name(string $fieldname): string {
global $DB;
$attemptname = $fieldname;
// Check if this profilefield already exists.
$foundfieldname = false;
$i = 0;
do {
$profilefield = $DB->count_records('user_info_field', ['shortname' => $attemptname]);
if ($profilefield > 0) {
$i++;
$attemptname = $fieldname . $i;
} else {
set_config('profile_field_name', $attemptname, 'tool_moodlenet');
$foundfieldname = true;
}
} while (!$foundfieldname);
return $attemptname;
}
/**
* Gets the unique profile field used to hold the moodle net profile.
*
* @return string The profile field name being used on this site.
*/
public static function get_profile_field_name(): string {
return get_config('tool_moodlenet', 'profile_field_name');
}
/**
* Create a user profile field to hold the moodlenet profile information.
*
* @param int $categoryid The category to put this field into.
*/
public static function create_user_profile_text_field(int $categoryid): void {
global $CFG;
require_once($CFG->dirroot . '/user/profile/definelib.php');
require_once($CFG->dirroot . '/user/profile/field/text/define.class.php');
// Add our moodlenet profile field.
$profileclass = new \profile_define_text();
$data = (object) [
'shortname' => self::set_profile_field_name('mnetprofile'),
'name' => get_string('mnetprofile', 'tool_moodlenet'),
'datatype' => 'text',
'description' => get_string('mnetprofiledesc', 'tool_moodlenet'),
'descriptionformat' => 1,
'categoryid' => $categoryid,
'signup' => 1,
'forceunique' => 1,
'visible' => 2,
'param1' => 30,
'param2' => 2048
];
$profileclass->define_save($data);
}
/**
* Given our $moodlenetprofile let's cURL the domains' WebFinger endpoint
*
* @param moodlenet_user_profile $moodlenetprofile The moodlenet profile to get info from.
* @return array [bool, text, raw]
*/
public static function get_moodlenet_profile_link(moodlenet_user_profile $moodlenetprofile): array {
$domain = $moodlenetprofile->get_domain();
$username = $moodlenetprofile->get_username();
// Assumption: All MoodleNet instance's will contain a WebFinger validation script.
$url = "https://".$domain."/.well-known/webfinger?resource=acct:".$username."@".$domain;
$curl = new \curl();
$options = [
'CURLOPT_HEADER' => 0,
];
$content = $curl->get($url, null, $options);
$errno = $curl->get_errno();
$info = $curl->get_info();
// The base cURL seems fine, let's press on.
if (!$errno) {
// WebFinger gave us a 404 back so the user has no droids here.
if ($info['http_code'] >= 400) {
if ($info['http_code'] === 404) {
// User not found.
return [
'result' => false,
'message' => get_string('profilevalidationfail', 'tool_moodlenet'),
];
} else {
// There was some other error that was not a missing account.
return [
'result' => false,
'message' => get_string('profilevalidationerror', 'tool_moodlenet'),
];
}
}
// We must have a valid link so give it back to the user.
$data = json_decode($content);
return [
'result' => true,
'message' => get_string('profilevalidationpass', 'tool_moodlenet'),
'domain' => $data->aliases[0]
];
} else {
// There was some failure in curl so report it back.
return [
'result' => false,
'message' => get_string('profilevalidationerror', 'tool_moodlenet'),
];
}
}
}

View File

@ -0,0 +1,44 @@
<?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/>.
/**
* Tool Moodle.Net webservice definitions.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$functions = [
'tool_moodlenet_verify_webfinger' => [
'classname' => 'tool_moodlenet\external',
'methodname' => 'verify_webfinger',
'description' => 'Verify if the passed information resolves into a WebFinger profile URL',
'type' => 'read',
'ajax' => true,
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE]
],
'tool_moodlenet_search_courses' => [
'classname' => 'tool_moodlenet\external',
'methodname' => 'search_courses',
'description' => 'For some given input search for a course that matches',
'type' => 'read',
'ajax' => true,
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE]
],
];

View File

@ -0,0 +1,81 @@
<?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/>.
/**
* Upgrade script for tool_moodlenet.
*
* @package tool_moodlenet
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Upgrade the plugin.
*
* @param int $oldversion
* @return bool always true
*/
function xmldb_tool_moodlenet_upgrade(int $oldversion) {
global $CFG, $DB;
if ($oldversion < 2020060500) {
// Grab some of the old settings.
$categoryname = get_config('tool_moodlenet', 'profile_category');
$profilefield = get_config('tool_moodlenet', 'profile_field_name');
// Master version only!
// Find out if we have a custom profile field for moodle.net.
$sql = "SELECT f.*
FROM {user_info_field} f
JOIN {user_info_category} c ON c.id = f.categoryid and c.name = :categoryname
WHERE f.shortname = :name";
$params = [
'categoryname' => $categoryname,
'name' => $profilefield
];
$record = $DB->get_record_sql($sql, $params);
if (!empty($record)) {
$userentries = $DB->get_recordset('user_info_data', ['fieldid' => $record->id]);
$recordstodelete = [];
foreach ($userentries as $userentry) {
$data = (object) [
'id' => $userentry->userid,
'moodlenetprofile' => $userentry->data
];
$DB->update_record('user', $data, true);
$recordstodelete[] = $userentry->id;
}
$userentries->close();
// Remove the user profile data, fields, and category.
$DB->delete_records_list('user_info_data', 'id', $recordstodelete);
$DB->delete_records('user_info_field', ['id' => $record->id]);
$DB->delete_records('user_info_category', ['name' => $categoryname]);
unset_config('profile_field_name', 'tool_moodlenet');
unset_config('profile_category', 'tool_moodlenet');
}
upgrade_plugin_savepoint(true, 2020060500, 'tool', 'moodlenet');
}
return true;
}

View File

@ -0,0 +1,85 @@
<?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/>.
/**
* This is the main endpoint which MoodleNet instances POST to.
*
* MoodleNet instances send the user agent to this endpoint via a form POST.
* Then:
* 1. The POSTed resource information is put in a session store for cross-request access.
* 2. This page makes a GET request for admin/tool/moodlenet/index.php (the import confirmation page).
* 3. Then, depending on whether the user is authenticated, the user will either:
* - If not authenticated, they will be asked to login, after which they will see the confirmation page (leveraging $wantsurl).
* - If authenticated, they will see the confirmation page immediately.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_moodlenet\local\import_info;
use tool_moodlenet\local\remote_resource;
use tool_moodlenet\local\url;
require_once(__DIR__ . '/../../../config.php');
// The integration must be enabled for this import endpoint to be active.
if (!get_config('tool_moodlenet', 'enablemoodlenet')) {
print_error('moodlenetnotenabled', 'tool_moodlenet');
}
$resourceurl = required_param('resourceurl', PARAM_URL);
$resourceinfo = required_param('resource_info', PARAM_RAW);
$resourceinfo = json_decode($resourceinfo);
$type = optional_param('type', 'link', PARAM_TEXT);
$course = optional_param('course', 0, PARAM_INT);
$section = optional_param('section', 0, PARAM_INT);
// If course isn't provided, course and section are null.
if (empty($course)) {
$course = null;
$section = null;
}
$name = validate_param($resourceinfo->name, PARAM_TEXT);
$description = validate_param($resourceinfo->summary, PARAM_TEXT);
// Only accept POSTs.
if (!empty($_POST)) {
// Store information about the import of the resource for the current user.
$importconfig = (object) [
'course' => $course,
'section' => $section,
'type' => $type,
];
$metadata = (object) [
'name' => $name,
'description' => $description ?? ''
];
require_once($CFG->libdir . '/filelib.php');
$importinfo = new import_info(
$USER->id,
new remote_resource(new \curl(), new url($resourceurl), $metadata),
$importconfig
);
$importinfo->save();
// Redirect to the import confirmation page, detouring via the log in page if required.
redirect(new moodle_url('/admin/tool/moodlenet/index.php', ['id' => $importinfo->get_id()]));
}
// Invalid or missing POST data. Show an error to the user.
print_error('missinginvalidpostdata', 'tool_moodlenet');

View File

@ -0,0 +1,136 @@
<?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/>.
/**
* Landing page for all imports from MoodleNet.
*
* This page asks the user to confirm the import process, and takes them to the relevant next step.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_moodlenet\local\import_info;
use tool_moodlenet\local\import_backup_helper;
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot .'/course/lib.php');
$cancel = optional_param('cancel', null, PARAM_TEXT);
$continue = optional_param('continue', null, PARAM_TEXT);
$id = required_param('id', PARAM_ALPHANUM);
if (is_null($importinfo = import_info::load($id))) {
throw new moodle_exception('missinginvalidpostdata', 'tool_moodlenet');
}
// Access control.
require_login($importinfo->get_config()->course, false); // Course may be null here - that's ok.
if ($importinfo->get_config()->course) {
require_capability('moodle/course:manageactivities', context_course::instance($importinfo->get_config()->course));
}
if (!get_config('tool_moodlenet', 'enablemoodlenet')) {
print_error('moodlenetnotenabled', 'tool_moodlenet');
}
// Handle the form submits.
// This page POSTs to self to verify the sesskey for the confirm action.
// The next page will either be:
// - 1. The restore process for a course or module, if the file is an mbz file.
// - 2. The 'select a course' tool page, if course and section are not provided.
// - 3. The 'select what to do with the content' tool page, provided course and section are present.
// - 4. The dashboard, if the user decides to cancel and course or section is not found.
// - 5. The course home, if the user decides to cancel but the course and section are found.
if ($cancel) {
if (!empty($importinfo->get_config()->course)) {
$url = new \moodle_url('/course/view.php', ['id' => $importinfo->get_config()->course]);
} else {
$url = new \moodle_url('/');
}
redirect($url);
} else if ($continue) {
confirm_sesskey();
// Handle backups.
if (strtolower($importinfo->get_resource()->get_extension()) == 'mbz') {
if (empty($importinfo->get_config()->course)) {
// Find a course that the user has permission to upload a backup file.
// This is likely to be very slow on larger sites.
$context = import_backup_helper::get_context_for_user($USER->id);
if (is_null($context)) {
print_error('nopermissions', 'error', '', get_string('restore:uploadfile', 'core_role'));
}
} else {
$context = context_course::instance($importinfo->get_config()->course);
}
$importbackuphelper = new import_backup_helper($importinfo->get_resource(), $USER, $context);
$storedfile = $importbackuphelper->get_stored_file();
$url = new \moodle_url('/backup/restorefile.php', [
'component' => $storedfile->get_component(),
'filearea' => $storedfile->get_filearea(),
'itemid' => $storedfile->get_itemid(),
'filepath' => $storedfile->get_filepath(),
'filename' => $storedfile->get_filename(),
'filecontextid' => $storedfile->get_contextid(),
'contextid' => $context->id,
'action' => 'choosebackupfile'
]);
redirect($url);
}
// Handle adding files to a course.
// Course and section data present and confirmed. Redirect to the option select view.
if (!is_null($importinfo->get_config()->course) && !is_null($importinfo->get_config()->section)) {
redirect(new \moodle_url('/admin/tool/moodlenet/options.php', ['id' => $id]));
}
if (is_null($importinfo->get_config()->course)) {
redirect(new \moodle_url('/admin/tool/moodlenet/select.php', ['id' => $id]));
}
}
// Display the page.
$PAGE->set_context(context_system::instance());
$PAGE->set_pagelayout('base');
$PAGE->set_title(get_string('addingaresource', 'tool_moodlenet'));
$PAGE->set_heading(get_string('addingaresource', 'tool_moodlenet'));
$url = new moodle_url('/admin/tool/moodlenet/index.php');
$PAGE->set_url($url);
$renderer = $PAGE->get_renderer('core');
// Relevant confirmation form.
$context = $context = [
'resourceurl' => $importinfo->get_resource()->get_url()->get_value(),
'resourcename' => $importinfo->get_resource()->get_name(),
'resourcetype' => $importinfo->get_config()->type,
'sesskey' => sesskey()
];
if (!is_null($importinfo->get_config()->course) && !is_null($importinfo->get_config()->section)) {
$course = get_course($importinfo->get_config()->course);
$context = array_merge($context, [
'course' => $course->id,
'coursename' => $course->shortname,
'section' => $importinfo->get_config()->section
]);
}
echo $OUTPUT->header();
echo $renderer->render_from_template('tool_moodlenet/import_confirmation', $context);
echo $OUTPUT->footer();

View File

@ -0,0 +1,69 @@
<?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/>.
/**
* Strings for the tool_moodlenet component.
*
* @package tool_moodlenet
* @category string
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['addingaresource'] = 'Adding content from MoodleNet';
$string['aria:enterprofile'] = "Enter your MoodleNet profile URL";
$string['aria:footermessage'] = "Browse for content on MoodleNet";
$string['browsecontentmoodlenet'] = "Or browse for content on MoodleNet";
$string['clearsearch'] = "Clear search";
$string['connectandbrowse'] = "Connect to and browse:";
$string['defaultmoodlenet'] = "Default MoodleNet URL";
$string['defaultmoodlenet_desc'] = "The URL to either Moodle HQ's MoodleNet instance, or your preferred instance.";
$string['defaultmoodlenetname'] = "MoodleNet instance name";
$string['defaultmoodlenetname_desc'] = 'The name of either Moodle HQ\'s MoodleNet instance or your preferred MoodleNet instance to browse on.';
$string['enablemoodlenet'] = 'Enable MoodleNet integration';
$string['enablemoodlenet_desc'] = 'Enabling the integration allows users with the \'xx\' capability to browse MoodleNet from the
activity chooser and import MoodleNet resources into their course. It also allows users to push backups from MoodleNet into Moodle.
';
$string['errorduringdownload'] = 'An error occurred while downloading the file: {$a}';
$string['forminfo'] = "It will be automatically saved on your moodle profile.";
$string['footermessage'] = "Or browse for content on";
$string['instancedescription'] = "MoodleNet is an open social media platform for educators, with a focus on the collaborative curation of collections of open resources. ";
$string['instanceplaceholder'] = '@yourprofile@moodle.net';
$string['inputhelp'] = 'Or if you have a MoodleNet account already, enter your MoodleNet profile:';
$string['invalidmoodlenetprofile'] = '$userprofile is not correctly formatted';
$string['importconfirm'] = 'You are about to import the content "{$a->resourcename} ({$a->resourcetype})" into the course "{$a->coursename}". Are you sure you want to continue?';
$string['importconfirmnocourse'] = 'You are about to import the content "{$a->resourcename} ({$a->resourcetype})" into your site. Are you sure you want to continue?';
$string['importformatselectguidingtext'] = 'In which format would you like this content "{$a->name} ({$a->type})" to be added to your course?';
$string['importformatselectheader'] = 'Choose the content display format';
$string['missinginvalidpostdata'] = 'The resource information from MoodleNet is either missing, or is in an incorrect format.
If this happens repeatedly, please contact the site administrator.';
$string['mnetprofile'] = 'MoodleNet profile';
$string['mnetprofiledesc'] = '<p>Enter in your MoodleNet profile details here to be redirected to your profile while visiting MoodleNet.</p>';
$string['moodlenetsettings'] = 'MoodleNet settings';
$string['moodlenetnotenabled'] = 'The MoodleNet integration must be enabled before resource imports can be processed.
To enable this feature, see the \'enablemoodlenet\' setting.';
$string['notification'] = 'You are about to import the content "{$a->name} ({$a->type})" into your site. Select the course in which it should be added, or <a href="{$a->cancellink}">cancel</a>.';
$string['searchcourses'] = "Search courses";
$string['selectpagetitle'] = 'Select page';
$string['pluginname'] = 'MoodleNet';
$string['privacy:metadata'] = "The MoodleNet tool only facilitates communication with MoodleNet. It stores no data.";
$string['profilevalidationerror'] = 'There was a problem trying to validate your profile';
$string['profilevalidationfail'] = 'Please enter a valid MoodleNet profile';
$string['profilevalidationpass'] = 'Looks good!';
$string['saveandgo'] = "Save and go";
$string['uploadlimitexceeded'] = 'The file size {$a->filesize} exceeds the user upload limit of {$a->uploadlimit} bytes.';

View File

@ -0,0 +1,104 @@
<?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/>.
/**
* This page lists public api for tool_moodlenet plugin.
*
* @package tool_moodlenet
* @copyright 2020 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU
*/
defined('MOODLE_INTERNAL') || die;
use \core_course\local\entity\activity_chooser_footer;
/**
* The default endpoint to MoodleNet.
*/
define('MOODLENET_DEFAULT_ENDPOINT', "lms/moodle/search");
/**
* Generate the endpoint url to the user's moodlenet site.
*
* @param string $profileurl The user's moodlenet profile page
* @param int $course The moodle course the mnet resource will be added to
* @param int $section The section of the course will be added to. Defaults to the 0th element.
* @return string the resulting endpoint
* @throws moodle_exception
*/
function generate_mnet_endpoint(string $profileurl, int $course, int $section = 0) {
global $CFG;
$urlportions = explode('@', $profileurl);
$domain = end($urlportions);
$parsedurl = parse_url($domain);
$params = [
'site' => $CFG->wwwroot,
'course' => $course,
'section' => $section
];
$endpoint = new moodle_url(MOODLENET_DEFAULT_ENDPOINT, $params);
return (isset($parsedurl['scheme']) ? $domain : "https://$domain")."/{$endpoint->out(false)}";
}
/**
* Hooking function to build up the initial Activity Chooser footer information for MoodleNet
*
* @param int $courseid The course the user is currently in and wants to add resources to
* @param int $sectionid The section the user is currently in and wants to add resources to
* @return activity_chooser_footer
* @throws dml_exception
* @throws moodle_exception
*/
function tool_moodlenet_custom_chooser_footer(int $courseid, int $sectionid): activity_chooser_footer {
global $CFG, $USER, $OUTPUT;
$defaultlink = get_config('tool_moodlenet', 'defaultmoodlenet');
$enabled = get_config('tool_moodlenet', 'enablemoodlenet');
$advanced = false;
// We are in the MoodleNet lib. It is safe assume we have our own functions here.
$mnetprofile = \tool_moodlenet\profile_manager::get_moodlenet_user_profile($USER->id);
if ($mnetprofile !== null) {
$advanced = $mnetprofile->get_domain() ?? false;
}
$defaultlink = generate_mnet_endpoint($defaultlink, $courseid, $sectionid);
if ($advanced !== false) {
$advanced = generate_mnet_endpoint($advanced, $courseid, $sectionid);
}
$renderedfooter = $OUTPUT->render_from_template('tool_moodlenet/chooser_footer', (object)[
'enabled' => (bool)$enabled,
'generic' => $defaultlink,
'advanced' => $advanced,
'courseID' => $courseid,
'sectionID' => $sectionid,
'img' => $OUTPUT->image_url('MoodleNet', 'tool_moodlenet')->out(false),
]);
$renderedcarousel = $OUTPUT->render_from_template('tool_moodlenet/chooser_moodlenet', (object)[
'buttonName' => get_config('tool_moodlenet', 'defaultmoodlenetname'),
'generic' => $defaultlink,
'courseID' => $courseid,
'sectionID' => $sectionid,
'img' => $OUTPUT->image_url('MoodleNet', 'tool_moodlenet')->out(false),
]);
return new activity_chooser_footer(
'tool_moodlenet/instance_form',
$renderedfooter,
$renderedcarousel
);
}

View File

@ -0,0 +1,128 @@
<?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/>.
/**
* Page to select WHAT to do with a given resource stored on MoodleNet.
*
* This collates and presents the same options as a user would see for a drag and drop upload.
* That is, it leverages the dndupload_register() hooks and delegates the resource handling to the dndupload_handle hooks.
*
* This page requires a course, section an resourceurl to be provided via import_info.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_moodlenet\local\import_handler_registry;
use tool_moodlenet\local\import_processor;
use tool_moodlenet\local\import_info;
use tool_moodlenet\local\import_strategy_file;
use tool_moodlenet\local\import_strategy_link;
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->dirroot . '/course/lib.php');
$module = optional_param('module', null, PARAM_PLUGIN);
$import = optional_param('import', null, PARAM_ALPHA);
$cancel = optional_param('cancel', null, PARAM_ALPHA);
$id = required_param('id', PARAM_ALPHANUM);
if (is_null($importinfo = import_info::load($id))) {
throw new moodle_exception('missinginvalidpostdata', 'tool_moodlenet');
}
// Resolve course and section params.
// If course is not already set in the importinfo, we require it in the URL params.
$config = $importinfo->get_config();
if (!isset($config->course)) {
$course = required_param('course', PARAM_INT);
$config->course = $course;
$config->section = 0;
$importinfo->set_config($config);
$importinfo->save();
}
// Access control.
require_login($config->course, false);
require_capability('moodle/course:manageactivities', context_course::instance($config->course));
if (!get_config('tool_moodlenet', 'enablemoodlenet')) {
print_error('moodlenetnotenabled', 'tool_moodlenet');
}
// If the user cancelled, break early.
if ($cancel) {
redirect(new moodle_url('/course/view.php', ['id' => $config->course]));
}
// Set up required objects.
$course = get_course($config->course);
$handlerregistry = new import_handler_registry($course, $USER);
switch ($config->type) {
case 'file':
$strategy = new import_strategy_file();
break;
case 'link':
default:
$strategy = new import_strategy_link();
break;
}
if ($import && $module) {
confirm_sesskey();
$handlerinfo = $handlerregistry->get_resource_handler_for_mod_and_strategy($importinfo->get_resource(), $module, $strategy);
if (is_null($handlerinfo)) {
throw new coding_exception("Invalid handler '$module'. The import handler could not be found.");
}
$importproc = new import_processor($course, $config->section, $importinfo->get_resource(), $handlerinfo, $handlerregistry);
$importproc->process();
$importinfo->purge(); // We don't need information about the import any more.
redirect(new moodle_url('/course/view.php', ['id' => $course->id]));
}
// Setup the page and display the form.
$PAGE->set_context(context_course::instance($course->id));
$PAGE->set_pagelayout('base');
$PAGE->set_title(get_string('coursetitle', 'moodle', array('course' => $course->fullname)));
$PAGE->set_heading($course->fullname);
$PAGE->set_url(new moodle_url('/admin/tool/moodlenet/options.php'));
// Fetch the handlers supporting this resource. We'll display each of these as an option in the form.
$handlercontext = [];
foreach ($handlerregistry->get_resource_handlers_for_strategy($importinfo->get_resource(), $strategy) as $handler) {
$handlercontext[] = [
'module' => $handler->get_module_name(),
'message' => $handler->get_description(),
];
}
// Template context.
$context = [
'resourcename' => $importinfo->get_resource()->get_name(),
'resourcetype' => $importinfo->get_config()->type,
'resourceurl' => urlencode($importinfo->get_resource()->get_url()->get_value()),
'course' => $course->id,
'section' => $config->section,
'sesskey' => sesskey(),
'handlers' => $handlercontext,
'oneoption' => sizeof($handlercontext) === 1
];
echo $OUTPUT->header();
echo $PAGE->get_renderer('core')->render_from_template('tool_moodlenet/import_options_select', $context);
echo $OUTPUT->footer();

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

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

@ -0,0 +1,52 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="157 -1305 148 125" preserveAspectRatio="xMinYMid meet">
<defs>
<style>
.cls-1 {
clip-path: url(#clip-Courses);
}
.cls-2 {
fill: #eee;
}
.cls-3 {
fill: #c4c8cc;
}
.cls-4 {
fill: #fff;
}
</style>
<clipPath id="clip-Courses">
<rect x="157" y="-1305" width="148" height="125"/>
</clipPath>
</defs>
<g id="Courses" class="cls-1">
<g id="Group_44" data-name="Group 44" transform="translate(-268 -1781)">
<ellipse id="Ellipse_41" data-name="Ellipse 41" class="cls-2" cx="74" cy="14.785" rx="74" ry="14.785" transform="translate(425 571.43)"/>
<rect id="Rectangle_87" data-name="Rectangle 87" class="cls-3" width="95.097" height="110.215" transform="translate(451.909 476)"/>
<g id="Group_43" data-name="Group 43" transform="translate(464.04 494)">
<rect id="Rectangle_88" data-name="Rectangle 88" class="cls-4" width="31.043" height="34" transform="translate(0)"/>
<rect id="Rectangle_89" data-name="Rectangle 89" class="cls-4" width="31.043" height="34" transform="translate(0 42)"/>
<rect id="Rectangle_90" data-name="Rectangle 90" class="cls-4" width="31.067" height="34" transform="translate(39.005)"/>
<rect id="Rectangle_91" data-name="Rectangle 91" class="cls-4" width="31.067" height="34" transform="translate(39.005 42)"/>
<rect id="Rectangle_92" data-name="Rectangle 92" class="cls-3" width="23.023" height="3.18" transform="translate(3.081 16.549)"/>
<rect id="Rectangle_93" data-name="Rectangle 93" class="cls-3" width="23.023" height="3.18" transform="translate(3.081 58.549)"/>
<rect id="Rectangle_94" data-name="Rectangle 94" class="cls-3" width="23.023" height="3.18" transform="translate(43.122 16.549)"/>
<rect id="Rectangle_95" data-name="Rectangle 95" class="cls-3" width="23.023" height="3.18" transform="translate(43.122 58.549)"/>
<rect id="Rectangle_96" data-name="Rectangle 96" class="cls-3" width="14.014" height="3.18" transform="translate(3.081 21.825)"/>
<rect id="Rectangle_97" data-name="Rectangle 97" class="cls-3" width="18.845" height="3.18" transform="translate(3.081 26.825)"/>
<rect id="Rectangle_98" data-name="Rectangle 98" class="cls-3" width="14.014" height="3.18" transform="translate(3.081 63.825)"/>
<rect id="Rectangle_99" data-name="Rectangle 99" class="cls-3" width="18.845" height="3.18" transform="translate(3.081 68.825)"/>
<rect id="Rectangle_100" data-name="Rectangle 100" class="cls-3" width="14.014" height="3.18" transform="translate(43.122 21.825)"/>
<rect id="Rectangle_101" data-name="Rectangle 101" class="cls-3" width="18.845" height="3.18" transform="translate(43.122 26.825)"/>
<rect id="Rectangle_102" data-name="Rectangle 102" class="cls-3" width="14.014" height="3.18" transform="translate(43.122 63.825)"/>
<rect id="Rectangle_103" data-name="Rectangle 103" class="cls-3" width="18.845" height="3.18" transform="translate(43.122 68.825)"/>
<ellipse id="Ellipse_42" data-name="Ellipse 42" class="cls-3" cx="5.658" cy="5.652" rx="5.658" ry="5.652" transform="translate(3.003 3.55)"/>
<ellipse id="Ellipse_43" data-name="Ellipse 43" class="cls-3" cx="5.658" cy="5.652" rx="5.658" ry="5.652" transform="translate(3.003 45.55)"/>
<ellipse id="Ellipse_44" data-name="Ellipse 44" class="cls-3" cx="5.658" cy="5.652" rx="5.658" ry="5.652" transform="translate(43.044 3.55)"/>
<ellipse id="Ellipse_45" data-name="Ellipse 45" class="cls-3" cx="5.658" cy="5.652" rx="5.658" ry="5.652" transform="translate(43.044 45.55)"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,53 @@
<?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/>.
/**
* Select page.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use tool_moodlenet\local\import_info;
use tool_moodlenet\output\select_page;
require_once(__DIR__ . '/../../../config.php');
$id = required_param('id', PARAM_ALPHANUM);
// Access control.
require_login();
if (!get_config('tool_moodlenet', 'enablemoodlenet')) {
print_error('moodlenetnotenabled', 'tool_moodlenet');
}
if (is_null($importinfo = import_info::load($id))) {
throw new moodle_exception('missinginvalidpostdata', 'tool_moodlenet');
}
$PAGE->set_url('/admin/tool/moodlenet/select.php');
$PAGE->set_context(context_system::instance());
$PAGE->set_pagelayout('standard');
$PAGE->set_title(get_string('selectpagetitle', 'tool_moodlenet'));
$PAGE->set_heading(format_string($SITE->fullname));
echo $OUTPUT->header();
$renderable = new select_page($importinfo);
$renderer = $PAGE->get_renderer('tool_moodlenet');
echo $renderer->render($renderable);
echo $OUTPUT->footer();

View File

@ -0,0 +1,46 @@
<?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/>.
/**
* Puts the plugin actions into the admin settings tree.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
if ($hassiteconfig) {
// Create a MoodleNet category.
$ADMIN->add('root', new admin_category('moodlenet', get_string('pluginname', 'tool_moodlenet')));
// Our settings page.
$settings = new admin_settingpage('tool_moodlenet', get_string('moodlenetsettings', 'tool_moodlenet'));
$ADMIN->add('moodlenet', $settings);
$temp = new admin_setting_configcheckbox('tool_moodlenet/enablemoodlenet', get_string('enablemoodlenet', 'tool_moodlenet'),
new lang_string('enablemoodlenet_desc', 'tool_moodlenet'), 1, 1, 0);
$settings->add($temp);
$temp = new admin_setting_configtext('tool_moodlenet/defaultmoodlenetname',
get_string('defaultmoodlenetname', 'tool_moodlenet'), new lang_string('defaultmoodlenetname_desc', 'tool_moodlenet'),
'Moodle HQ MoodleNet');
$settings->add($temp);
$temp = new admin_setting_configtext('tool_moodlenet/defaultmoodlenet', get_string('defaultmoodlenet', 'tool_moodlenet'),
new lang_string('defaultmoodlenet_desc', 'tool_moodlenet'), 'https://home.moodle.net');
$settings->add($temp);
}

View File

@ -0,0 +1,46 @@
{{!
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 tool_moodlenet/chooser_footer
Chooser favourite template partial.
Example context (json):
{
}
}}
{{#enabled}}
<div class="w-100 d-flex px-2">
<a aria-label="{{#str}}aria:footermessage, tool_moodlenet{{/str}}"
class="d-inline my-auto mr-1"
{{#advanced}}
href="{{advanced}}"
target="_self"
{{/advanced}}
{{^advanced}}
href="#"
data-action="show-moodlenet"
data-courseid="{{courseID}}"
data-sectionID="{{sectionID}}"
{{/advanced}}
>
{{#str}} footermessage , tool_moodlenet{{/str}}
<span class="moodlenet-logo" aria-hidden="true">{{#pix}} MoodleNet, tool_moodlenet {{/pix}}</span>
</a>
</div>
{{/enabled}}

View File

@ -0,0 +1,28 @@
{{!
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 tool_moodlenet/chooser_footer_close_mnet
Chooser favourite template partial.
Example context (json):
{
}
}}
<button data-action="close-chooser-option-summary" class="closeoptionsummary btn btn-secondary mr-auto" tabindex="0">
{{#str}} back {{/str}}
</button>

View File

@ -0,0 +1,67 @@
{{!
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 tool_moodlenet/chooser_moodlenet
Chooser favourite template partial.
Example context (json):
{
}
}}
<div class="optionsummary" tabindex="-1" data-region="chooser-option-summary-container">
<div class="content text-left mb-5 px-5 py-4" data-region="chooser-option-summary-content-container">
<div data-region="moodle-net">
<div class="overlay-icon-container z-index-1 d-none" data-region="spinner"></div>
<img class="w-25 mb-4" aria-hidden="true" src="{{{img}}}">
<p>{{#str}} instancedescription, tool_moodlenet {{/str}}</p>
<p class="w-75 mx-auto mb-1 mt-5">{{#str}} connectandbrowse, tool_moodlenet {{/str}}</p>
<a class="btn btn-secondary d-block w-75 mx-auto mb-4"
data-action="browse"
href="{{{generic}}}"
>
{{{buttonName}}}
</a>
<div id="mnet-instance-form-{{uniqid}}" data-region="mnet-form">
<input type="hidden" name="sesskey" value="{{sesskey}}">
<div class="w-75 mx-auto my-3">
<p class="text-left">{{#str}}inputhelp, tool_moodlenet{{/str}}</p>
<div class="input-group">
<input type="text"
class="form-control"
data-var="mnet-link"
data-courseid="{{courseID}}"
data-sectionid="{{sectionID}}"
placeholder="{{#str}} instanceplaceholder, tool_moodlenet {{/str}}"
aria-label="{{#str}} aria:enterprofile, tool_moodlenet {{/str}}"
autocomplete="off"
>
<div class="input-group-append z-index-0">
<button class="btn btn-secondary"
data-action="submit" id="button-addon2"
>
{{#str}} saveandgo, tool_moodlenet {{/str}}
</button>
</div>
</div>
<p class="text-left" aria-live="assertive" data-region="validation-area"></p>
<p class="text-left">{{#str}} forminfo, tool_moodlenet {{/str}}</p>
</div>
</div>
</div>
</div>
</div>

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_moodlenet/import_confirmation
MoodleNet import confirmation template.
The purpose of this template is to present the user with a confirm/cancel dialog-like page.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* resourceurl The URL to the remote resource on MoodleNet.
* resourcename The name of the remote resource on MoodleNet.
* sesskey The CSRF token, as per sesskey()
Example context (json):
{
"course": 33,
"coursename": "Introduction to quantum physics",
"section": 0,
"resourceurl": "http://example.com/test.png",
"resourcename": "test.png",
"sesskey": "abc123"
}
}}
<div class="generalbox modal modal-dialog modal-in-page show">
<div class="box py-3 modal-content">
<form action="#" method="post">
{{#course}}
<input type="hidden" name="course" value="{{course}}">
{{/course}}
<input type="hidden" name="section" value="{{section}}">
<input type="hidden" name="resourceurl" value="{{resourceurl}}">
<input type="hidden" name="sesskey" value="{{sesskey}}">
<div class="box py-3 modal-header p-x-1">
<h4>{{#str}}confirm, core{{/str}}</h4>
</div>
<div class="box py-3 modal-body">
{{#course}}
{{#str}}importconfirm, tool_moodlenet, {"resourcename": {{#quote}}{{resourcename}}{{/quote}}, "resourcetype": {{#quote}}{{resourcetype}}{{/quote}}, "coursename": {{#quote}}{{coursename}}{{/quote}} }{{/str}}
{{/course}}
{{^course}}
{{#str}}importconfirmnocourse, tool_moodlenet, {"resourcename": {{#quote}}{{resourcename}}{{/quote}}, "resourcetype": {{#quote}}{{resourcetype}}{{/quote}} }{{/str}}
{{/course}}
</div>
<div class="box py-3 modal-footer">
<div class="buttons">
<input class="btn btn-secondary" type="submit" name="cancel" value="{{#str}}cancel, core{{/str}}">
<input class="btn btn-primary" type="submit" name="continue" value="{{#str}}confirm, core{{/str}}">
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,80 @@
{{!
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 tool_moodlenet/import_options_select
MoodleNet import options template.
The purpose of this template is to render an list of import options as radio-button-like controls.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* course The course id.
* section The sectio id.
* resourceurl The URL to the remote resource on MoodleNet.
* resourcename The name of the remote resource on MoodleNet.
* sesskey The CSRF token, as per sesskey()
* handlers The array of handler options to present the user with
Example context (json):
{
"course": 33,
"coursename": "Introduction to quantum physics",
"section": 0,
"resourceurl": "http://example.com/test.png",
"resourcename": "A test image",
"sesskey": "abc123",
"handlers": [
{
"module": "label",
"message": "Add media to the course page"
}
]
}
}}
<div class="generalbox modal modal-dialog modal-in-page show">
<div class="box py-3 modal-content">
<form action="#" method="post">
<input type="hidden" name="course" value="{{course}}">
<input type="hidden" name="section" value="{{section}}">
<input type="hidden" name="resourceurl" value="{{resourceurl}}">
<input type="hidden" name="sesskey" value="{{sesskey}}">
<div class="box py-3 modal-header p-x-1">
<h4>{{#str}}importformatselectheader, tool_moodlenet{{/str}}</h4>
</div>
<div class="box py-3 modal-body">
{{#str}}importformatselectguidingtext, tool_moodlenet, {"name": {{#quote}}{{resourcename}}{{/quote}}, "type": {{#quote}}{{resourcetype}}{{/quote}} }{{/str}}
<br><br>
{{#handlers}}
<input id="{{module}}_option" name="module" type="radio" value="{{module}}" {{#oneoption}}checked="checked"{{/oneoption}}>&nbsp;<label for="{{module}}_option">{{message}}</label>
<br>
{{/handlers}}
</div>
<div class="box py-3 modal-footer">
<div class="buttons">
<input class="btn btn-secondary" type="submit" name="cancel" value="Cancel">
<input class="btn btn-primary" type="submit" name="import" value="Continue">
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,58 @@
{{!
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 tool_moodlenet/select_page
This template renders the course selection page for the MoodleNet tool.
Example context (json):
{
"name": "A cat picture",
"cancellink": "https://moodlesite/my"
}
}}
<div data-region="moodle-net-select">
<div class="alert alert-primary" role="alert">
{{#str}} notification, tool_moodlenet, {"name": {{#quote}}{{name}}{{/quote}}, "cancellink": {{#quote}}{{cancellink}}{{/quote}}, "type": {{#quote}}{{type}}{{/quote}} } {{/str}}
</div>
<h3>{{#str}} selectacourse {{/str}}</h3>
<div class="searchbar input-group w-50" role="search">
<label for="searchinput">
<span class="sr-only">{{#str}} searchcourses, tool_moodlenet {{/str}}</span>
</label>
<input type="text"
data-region="search-input"
id="searchinput"
class="form-control form-control-lg searchinput border-right-0 px-3 py-2"
placeholder="{{#str}} search, core {{/str}}"
name="search"
autocomplete="off"
>
<div class="searchbar-append d-flex border border-secondary border-left-0 px-3 py-2">
<div data-region="search-icon">
{{#pix}} a/search, core {{/pix}}
</div>
<div class="clear d-none">
<button class="btn p-0" data-region="clear-icon">
<span class="d-flex" aria-hidden="true">{{#pix}} e/cancel, core {{/pix}}</span>
<span class="sr-only">{{#str}} clearsearch, tool_moodlenet {{/str}}</span>
</button>
</div>
</div>
</div>
<div class="my-4" data-region="mnet-courses" aria-live="polite"></div>
</div>

View File

@ -0,0 +1,54 @@
{{!
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 tool_moodlenet/view-cards
This template renders the cards view for the MoodleNet tool.
Example context (json):
{
"courses": [
{
"name": "Assignment due 1",
"viewurl": "https://moodlesite/course/view.php?id=2",
"courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg",
"fullname": "course 3",
"coursecategory": "Miscellaneous",
"visible": true
}
]
}
}}
{{< core_course/coursecards }}
{{$coursename}}
<span class="multiline">
{{#shortentext}}50, {{{fullname}}} {{/shortentext}}
</span>
{{/coursename}}
{{$coursecategory}}
<span class="sr-only">
{{#str}}aria:coursecategory, core_course{{/str}}
</span>
<span class="categoryname text-truncate">
{{{coursecategory}}}
</span>
{{/coursecategory}}
{{$divider}}
<div class="pl-1 pr-1">|</div>
{{/divider}}
{{/ core_course/coursecards }}

View File

@ -0,0 +1,84 @@
<?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/>.
/**
* Unit tests for the import_backup_helper
*
* @package tool_moodlenet
* @category test
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
/**
* Class import_backup_helper tests
*/
class tool_moodlenet_import_backup_helper_testcase extends advanced_testcase {
/**
* Test that the first available context with the capability to upload backup files is returned.
*/
public function test_get_context_for_user() {
global $DB;
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$user4 = $this->getDataGenerator()->create_user();
$user5 = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
$this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
$this->getDataGenerator()->enrol_user($user2->id, $course->id, 'editingteacher');
$this->getDataGenerator()->enrol_user($user5->id, $course->id, 'student');
$category = $this->getDataGenerator()->create_category();
$rolerecord = $DB->get_record('role', ['shortname' => 'manager']);
$categorycontext = context_coursecat::instance($category->id);
$this->getDataGenerator()->role_assign($rolerecord->id, $user3->id, $categorycontext->id);
$this->getDataGenerator()->role_assign($rolerecord->id, $user5->id, $categorycontext->id);
$roleid = $this->getDataGenerator()->create_role();
$sitecontext = context_system::instance();
assign_capability('moodle/restore:uploadfile', CAP_ALLOW, $roleid, $sitecontext->id, true);
accesslib_clear_all_caches_for_unit_testing();
$this->getDataGenerator()->role_assign($roleid, $user4->id, $sitecontext->id);
$result = \tool_moodlenet\local\import_backup_helper::get_context_for_user($user1->id);
$this->assertNull($result);
$result = \tool_moodlenet\local\import_backup_helper::get_context_for_user($user2->id);
$this->assertEquals($result, $coursecontext);
$this->assertEquals(CONTEXT_COURSE, $result->contextlevel);
$result = \tool_moodlenet\local\import_backup_helper::get_context_for_user($user3->id);
$this->assertEquals($result, $categorycontext);
$this->assertEquals(CONTEXT_COURSECAT, $result->contextlevel);
$result = \tool_moodlenet\local\import_backup_helper::get_context_for_user($user4->id);
$this->assertEquals($result, $sitecontext);
$this->assertEquals(CONTEXT_SYSTEM, $result->contextlevel);
$result = \tool_moodlenet\local\import_backup_helper::get_context_for_user($user5->id);
$this->assertEquals($result, $categorycontext);
$this->assertEquals(CONTEXT_COURSECAT, $result->contextlevel);
}
}

View File

@ -0,0 +1,75 @@
<?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/>.
/**
* Unit tests for the import_handler_info class.
*
* @package tool_moodlenet
* @category test
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local\tests;
use tool_moodlenet\local\import_handler_info;
use tool_moodlenet\local\import_strategy;
use tool_moodlenet\local\import_strategy_file;
defined('MOODLE_INTERNAL') || die();
/**
* Class tool_moodlenet_import_handler_info_testcase, providing test cases for the import_handler_info class.
*/
class tool_moodlenet_import_handler_info_testcase extends \advanced_testcase {
/**
* Test init and the getters.
*
* @dataProvider handler_info_data_provider
* @param string $modname the name of the mod.
* @param string $description description of the mod.
* @param bool $expectexception whether we expect an exception during init or not.
*/
public function test_initialisation($modname, $description, $expectexception) {
$this->resetAfterTest();
// Skip those cases we cannot init.
if ($expectexception) {
$this->expectException(\coding_exception::class);
$handlerinfo = new import_handler_info($modname, $description, new import_strategy_file());
}
$handlerinfo = new import_handler_info($modname, $description, new import_strategy_file());
$this->assertEquals($modname, $handlerinfo->get_module_name());
$this->assertEquals($description, $handlerinfo->get_description());
$this->assertInstanceOf(import_strategy::class, $handlerinfo->get_strategy());
}
/**
* Data provider for creation of import_handler_info objects.
*
* @return array the data for creation of the info object.
*/
public function handler_info_data_provider() {
return [
'All data present' => ['label', 'Add a label to the course', false],
'Empty module name' => ['', 'Add a file resource to the course', true],
'Empty description' => ['resource', '', true],
];
}
}

View File

@ -0,0 +1,119 @@
<?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/>.
/**
* Unit tests for the import_handler_registry class.
*
* @package tool_moodlenet
* @category test
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local\tests;
use tool_moodlenet\local\import_handler_registry;
use tool_moodlenet\local\import_handler_info;
use tool_moodlenet\local\import_strategy_file;
use tool_moodlenet\local\import_strategy_link;
use tool_moodlenet\local\remote_resource;
use tool_moodlenet\local\url;
defined('MOODLE_INTERNAL') || die();
/**
* Class tool_moodlenet_import_handler_registry_testcase, providing test cases for the import_handler_registry class.
*/
class tool_moodlenet_import_handler_registry_testcase extends \advanced_testcase {
/**
* Test confirming the behaviour of get_resource_handlers_for_strategy with different params.
*/
public function test_get_resource_handlers_for_strategy() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$ihr = new import_handler_registry($course, $teacher);
$resource = new remote_resource(
new \curl(),
new url('http://example.org'),
(object) [
'name' => 'Resource name',
'description' => 'Resource description'
]
);
$handlers = $ihr->get_resource_handlers_for_strategy($resource, new import_strategy_file());
$this->assertIsArray($handlers);
foreach ($handlers as $handler) {
$this->assertInstanceOf(import_handler_info::class, $handler);
}
}
/**
* Test confirming that the results are scoped to the provided user.
*/
public function test_get_resource_handlers_for_strategy_user_scoping() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$studentihr = new import_handler_registry($course, $student);
$teacherihr = new import_handler_registry($course, $teacher);
$resource = new remote_resource(
new \curl(),
new url('http://example.org'),
(object) [
'name' => 'Resource name',
'description' => 'Resource description'
]
);
$this->assertEmpty($studentihr->get_resource_handlers_for_strategy($resource, new import_strategy_file()));
$this->assertNotEmpty($teacherihr->get_resource_handlers_for_strategy($resource, new import_strategy_file()));
}
/**
* Test confirming that we can find a unique handler based on the module and strategy name.
*/
public function test_get_resource_handler_for_module_and_strategy() {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$ihr = new import_handler_registry($course, $teacher);
$resource = new remote_resource(
new \curl(),
new url('http://example.org'),
(object) [
'name' => 'Resource name',
'description' => 'Resource description'
]
);
// Resource handles every file type, so we'll always be able to find that unique handler when looking.
$handler = $ihr->get_resource_handler_for_mod_and_strategy($resource, 'resource', new import_strategy_file());
$this->assertInstanceOf(import_handler_info::class, $handler);
// URL handles every resource, so we'll always be able to find that unique handler when looking with a link strategy.
$handler = $ihr->get_resource_handler_for_mod_and_strategy($resource, 'url', new import_strategy_link());
$this->assertInstanceOf(import_handler_info::class, $handler);
$this->assertEquals('url', $handler->get_module_name());
$this->assertInstanceOf(import_strategy_link::class, $handler->get_strategy());
}
}

View File

@ -0,0 +1,105 @@
<?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/>.
/**
* Unit tests for the import_info class.
*
* @package tool_moodlenet
* @category test
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local\tests;
use tool_moodlenet\local\import_info;
use tool_moodlenet\local\remote_resource;
use tool_moodlenet\local\url;
defined('MOODLE_INTERNAL') || die();
/**
* Class tool_moodlenet_import_info_testcase, providing test cases for the import_info class.
*/
class tool_moodlenet_import_info_testcase extends \advanced_testcase {
/**
* Create some test objects.
*
* @return array
*/
protected function create_test_info(): array {
$user = $this->getDataGenerator()->create_user();
$resource = new remote_resource(new \curl(),
new url('http://example.org'),
(object) [
'name' => 'Resource name',
'description' => 'Resource summary'
]
);
$importinfo = new import_info($user->id, $resource, (object)[]);
return [$user, $resource, $importinfo];
}
/**
* Test for creation and getters.
*/
public function test_getters() {
$this->resetAfterTest();
[$user, $resource, $importinfo] = $this->create_test_info();
$this->assertEquals($resource, $importinfo->get_resource());
$this->assertEquals(new \stdClass(), $importinfo->get_config());
$this->assertNotEmpty($importinfo->get_id());
}
/**
* Test for setters.
*/
public function test_set_config() {
$this->resetAfterTest();
[$user, $resource, $importinfo] = $this->create_test_info();
$config = $importinfo->get_config();
$this->assertEquals(new \stdClass(), $config);
$config->course = 3;
$config->section = 1;
$importinfo->set_config($config);
$this->assertEquals((object) ['course' => 3, 'section' => 1], $importinfo->get_config());
}
/**
* Verify the object can be stored and loaded.
*/
public function test_persistence() {
$this->resetAfterTest();
[$user, $resource, $importinfo] = $this->create_test_info();
// Nothing to load initially since nothing has been saved.
$loadedinfo = import_info::load($importinfo->get_id());
$this->assertNull($loadedinfo);
// Now, save and confirm we can load the data into a new object.
$importinfo->save();
$loadedinfo2 = import_info::load($importinfo->get_id());
$this->assertEquals($importinfo, $loadedinfo2);
// Purge and confirm the load returns null now.
$importinfo->purge();
$loadedinfo3 = import_info::load($importinfo->get_id());
$this->assertNull($loadedinfo3);
}
}

View File

@ -0,0 +1,156 @@
<?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/>.
/**
* Unit tests for the import_processor class.
*
* @package tool_moodlenet
* @category test
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local\tests;
use tool_moodlenet\local\import_handler_registry;
use tool_moodlenet\local\import_processor;
use tool_moodlenet\local\import_strategy_file;
use tool_moodlenet\local\import_strategy_link;
use tool_moodlenet\local\remote_resource;
use tool_moodlenet\local\url;
defined('MOODLE_INTERNAL') || die();
/**
* Class tool_moodlenet_import_processor_testcase, providing test cases for the import_processor class.
*/
class tool_moodlenet_import_processor_testcase extends \advanced_testcase {
/**
* An integration test, this confirms the ability to construct an import processor and run the import for the current user.
*/
public function test_process_valid_resource() {
$this->resetAfterTest();
// Set up a user as a teacher in a course.
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$section = 0;
$this->setUser($teacher);
// Set up the import, using a mod_resource handler for the html extension.
$resourceurl = $this->getExternalTestFileUrl('/test.html');
$remoteresource = new remote_resource(
new \curl(),
new url($resourceurl),
(object) [
'name' => 'Resource name',
'description' => 'Resource description'
]
);
$handlerregistry = new import_handler_registry($course, $teacher);
$handlerinfo = $handlerregistry->get_resource_handler_for_mod_and_strategy($remoteresource, 'resource',
new import_strategy_file());
$importproc = new import_processor($course, $section, $remoteresource, $handlerinfo, $handlerregistry);
// Import the file.
$importproc->process();
// Verify there is a new mod_resource created with correct name, description and containing the test.html file.
$modinfo = get_fast_modinfo($course, $teacher->id);
$cms = $modinfo->get_instances();
$this->assertArrayHasKey('resource', $cms);
$cminfo = array_shift($cms['resource']);
$this->assertEquals('Resource name', $cminfo->get_formatted_name());
$cm = get_coursemodule_from_id('', $cminfo->id, 0, false, MUST_EXIST);
list($cm, $context, $module, $data, $cw) = get_moduleinfo_data($cminfo, $course);
$this->assertEquals($remoteresource->get_description(), $data->intro);
$fs = get_file_storage();
$files = $fs->get_area_files(\context_module::instance($cminfo->id)->id, 'mod_resource', 'content', false,
'sortorder DESC, id ASC', false);
$file = reset($files);
$this->assertEquals('test.html', $file->get_filename());
$this->assertEquals('text/html', $file->get_mimetype());
}
/**
* Test confirming that an exception is thrown when trying to process a resource which does not exist.
*/
public function test_process_invalid_resource() {
$this->resetAfterTest();
// Set up a user as a teacher in a course.
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$section = 0;
$this->setUser($teacher);
// Set up the import, using a mod_resource handler for the html extension.
$resourceurl = $this->getExternalTestFileUrl('/test.htmlzz');
$remoteresource = new remote_resource(
new \curl(),
new url($resourceurl),
(object) [
'name' => 'Resource name',
'description' => 'Resource description'
]
);
$handlerregistry = new import_handler_registry($course, $teacher);
$handlerinfo = $handlerregistry->get_resource_handler_for_mod_and_strategy($remoteresource, 'resource',
new import_strategy_file());
$importproc = new import_processor($course, $section, $remoteresource, $handlerinfo, $handlerregistry);
// Import the file.
$this->expectException(\coding_exception::class);
$importproc->process();
}
/**
* Test confirming that imports can be completed using alternative import strategies.
*/
public function test_process_alternative_import_strategies() {
$this->resetAfterTest();
// Set up a user as a teacher in a course.
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$section = 0;
$this->setUser($teacher);
// Set up the import, using a mod_url handler and the link import strategy.
$remoteresource = new remote_resource(
new \curl(),
new url('http://example.com/cats.pdf'),
(object) [
'name' => 'Resource name',
'description' => 'Resource description'
]
);
$handlerregistry = new import_handler_registry($course, $teacher);
$handlerinfo = $handlerregistry->get_resource_handler_for_mod_and_strategy($remoteresource, 'url',
new import_strategy_link());
$importproc = new import_processor($course, $section, $remoteresource, $handlerinfo, $handlerregistry);
// Import the resource as a link.
$importproc->process();
// Verify there is a new mod_url created with name 'cats' and containing the URL of the resource.
$modinfo = get_fast_modinfo($course, $teacher->id);
$cms = $modinfo->get_instances();
$this->assertArrayHasKey('url', $cms);
$cminfo = array_shift($cms['url']);
$this->assertEquals('Resource name', $cminfo->get_formatted_name());
}
}

View File

@ -0,0 +1,80 @@
<?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/>.
/**
* Unit tests for tool_moodlenet lib
*
* @package tool_moodlenet
* @copyright 2020 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/admin/tool/moodlenet/lib.php');
/**
* Test moodlenet functions
*/
class tool_moodlenet_lib_testcase extends advanced_testcase {
/**
* Test the generate_mnet_endpoint function
*
* @dataProvider get_endpoints_provider
* @param string $profileurl
* @param int $course
* @param int $section
* @param string $expected
*/
public function test_generate_mnet_endpoint($profileurl, $course, $section, $expected) {
$endpoint = generate_mnet_endpoint($profileurl, $course, $section);
$this->assertEquals($expected, $endpoint);
}
/**
* Dataprovider for test_generate_mnet_endpoint
*
* @return array
*/
public function get_endpoints_provider() {
global $CFG;
return [
[
'@name@domain.name',
1,
2,
'https://domain.name/' . MOODLENET_DEFAULT_ENDPOINT . '?site=' . urlencode($CFG->wwwroot)
. '&course=1&section=2'
],
[
'@profile@name@domain.name',
1,
2,
'https://domain.name/' . MOODLENET_DEFAULT_ENDPOINT . '?site=' . urlencode($CFG->wwwroot)
. '&course=1&section=2'
],
[
'https://domain.name',
1,
2,
'https://domain.name/' . MOODLENET_DEFAULT_ENDPOINT . '?site=' . urlencode($CFG->wwwroot)
. '&course=1&section=2'
]
];
}
}

View File

@ -0,0 +1,142 @@
<?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/>.
/**
* Unit tests for the profile manager
*
* @package tool_moodlenet
* @category test
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
/**
* Class profile_manager tests
*/
class tool_moodlenet_profile_manager_testcase extends advanced_testcase {
/**
* Test that on this site we use the user table to hold moodle net profile information.
*/
public function test_official_profile_exists() {
$this->assertTrue(\tool_moodlenet\profile_manager::official_profile_exists());
}
/**
* Test a null is returned when the user's mnet profile field is not set.
*/
public function test_get_moodlenet_user_profile_no_profile_set() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$result = \tool_moodlenet\profile_manager::get_moodlenet_user_profile($user->id);
$this->assertNull($result);
}
/**
* Test a null is returned when the user's mnet profile field is not set.
*/
public function test_moodlenet_user_profile_creation_no_profile_set() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$this->expectException(moodle_exception::class);
$this->expectExceptionMessage(get_string('invalidmoodlenetprofile', 'tool_moodlenet'));
$result = new \tool_moodlenet\moodlenet_user_profile("", $user->id);
}
/**
* Test the return of a moodle net profile.
*/
public function test_get_moodlenet_user_profile() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user(['moodlenetprofile' => '@matt@hq.mnet']);
$result = \tool_moodlenet\profile_manager::get_moodlenet_user_profile($user->id);
$this->assertEquals($user->moodlenetprofile, $result->get_profile_name());
}
/**
* Test the creation of a user profile category.
*/
public function test_create_user_profile_category() {
global $DB;
$this->resetAfterTest();
$basecategoryname = get_string('pluginname', 'tool_moodlenet');
\tool_moodlenet\profile_manager::create_user_profile_category();
$categoryname = \tool_moodlenet\profile_manager::get_category_name();
$this->assertEquals($basecategoryname, $categoryname);
\tool_moodlenet\profile_manager::create_user_profile_category();
$recordcount = $DB->count_records('user_info_category', ['name' => $basecategoryname]);
$this->assertEquals(1, $recordcount);
// Test the duplication of categories to ensure a unique name is always used.
$categoryname = \tool_moodlenet\profile_manager::get_category_name();
$this->assertEquals($basecategoryname . 1, $categoryname);
\tool_moodlenet\profile_manager::create_user_profile_category();
$categoryname = \tool_moodlenet\profile_manager::get_category_name();
$this->assertEquals($basecategoryname . 2, $categoryname);
}
/**
* Test the creating of the custom user profile field to hold the moodle net profile.
*/
public function test_create_user_profile_text_field() {
global $DB;
$this->resetAfterTest();
$shortname = 'mnetprofile';
$categoryid = \tool_moodlenet\profile_manager::create_user_profile_category();
\tool_moodlenet\profile_manager::create_user_profile_text_field($categoryid);
$record = $DB->get_record('user_info_field', ['shortname' => $shortname]);
$this->assertEquals($shortname, $record->shortname);
$this->assertEquals($categoryid, $record->categoryid);
// Test for a unique name if 'mnetprofile' is already in use.
\tool_moodlenet\profile_manager::create_user_profile_text_field($categoryid);
$profilename = \tool_moodlenet\profile_manager::get_profile_field_name();
$this->assertEquals($shortname . 1, $profilename);
\tool_moodlenet\profile_manager::create_user_profile_text_field($categoryid);
$profilename = \tool_moodlenet\profile_manager::get_profile_field_name();
$this->assertEquals($shortname . 2, $profilename);
}
/**
* Test that the user moodlenet profile is saved.
*/
public function test_save_moodlenet_user_profile() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$profilename = '@matt@hq.mnet';
$moodlenetprofile = new \tool_moodlenet\moodlenet_user_profile($profilename, $user->id);
\tool_moodlenet\profile_manager::save_moodlenet_user_profile($moodlenetprofile);
$userdata = \core_user::get_user($user->id);
$this->assertEquals($profilename, $userdata->moodlenetprofile);
}
}

View File

@ -0,0 +1,115 @@
<?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/>.
/**
* Unit tests for the remote_resource class.
*
* @package tool_moodlenet
* @category test
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local\tests;
use tool_moodlenet\local\remote_resource;
use tool_moodlenet\local\url;
defined('MOODLE_INTERNAL') || die();
/**
* Class tool_moodlenet_remote_resource_testcase, providing test cases for the remote_resource class.
*/
class tool_moodlenet_remote_resource_testcase extends \advanced_testcase {
/**
* Test getters.
*
* @dataProvider remote_resource_data_provider
* @param string $url the url of the resource.
* @param string $metadata the resource metadata like name, description, etc.
* @param string $expectedextension the extension we expect to find when querying the remote resource.
*/
public function test_getters($url, $metadata, $expectedextension) {
$this->resetAfterTest();
$remoteres = new remote_resource(new \curl(), new url($url), $metadata);
$this->assertEquals(new url($url), $remoteres->get_url());
$this->assertEquals($metadata->name, $remoteres->get_name());
$this->assertEquals($metadata->description, $remoteres->get_description());
$this->assertEquals($expectedextension, $remoteres->get_extension());
}
/**
* Data provider generating remote urls.
*
* @return array
*/
public function remote_resource_data_provider() {
return [
'With filename and extension' => [
$this->getExternalTestFileUrl('/test.html'),
(object) [
'name' => 'Test html file',
'description' => 'Full description of the html file'
],
'html'
],
'With filename only' => [
'http://example.com/path/file',
(object) [
'name' => 'Test html file',
'description' => 'Full description of the html file'
],
''
]
];
}
/**
* Test confirming the network based operations of a remote_resource.
*/
public function test_network_features() {
$url = $this->getExternalTestFileUrl('/test.html');
$nonexistenturl = $this->getExternalTestFileUrl('/test.htmlzz');
$remoteres = new remote_resource(
new \curl(),
new url($url),
(object) [
'name' => 'Test html file',
'description' => 'Some description'
]
);
$nonexistentremoteres = new remote_resource(
new \curl(),
new url($nonexistenturl),
(object) [
'name' => 'Test html file',
'description' => 'Some description'
]
);
$this->assertGreaterThan(0, $remoteres->get_download_size());
[$path, $name] = $remoteres->download_to_requestdir();
$this->assertIsString($path);
$this->assertEquals('test.html', $name);
$this->assertFileExists($path . '/' . $name);
$this->expectException(\coding_exception::class);
$nonexistentremoteres->get_download_size();
}
}

View File

@ -0,0 +1,103 @@
<?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/>.
/**
* Unit tests for the url class.
*
* @package tool_moodlenet
* @category test
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local\tests;
use tool_moodlenet\local\url;
defined('MOODLE_INTERNAL') || die();
/**
* Class tool_moodlenet_url_testcase, providing test cases for the url class.
*/
class tool_moodlenet_url_testcase extends \advanced_testcase {
/**
* Test the parsing to host + path components.
*
* @dataProvider url_provider
* @param string $urlstring The full URL string
* @param string $host the expected host component of the URL.
* @param string $path the expected path component of the URL.
* @param bool $exception whether or not an exception is expected during construction.
*/
public function test_parsing($urlstring, $host, $path, $exception) {
if ($exception) {
$this->expectException(\coding_exception::class);
$url = new url($urlstring);
return;
}
$url = new url($urlstring);
$this->assertEquals($urlstring, $url->get_value());
$this->assertEquals($host, $url->get_host());
$this->assertEquals($path, $url->get_path());
}
/**
* Data provider.
*
* @return array
*/
public function url_provider() {
return [
'No path' => [
'url' => 'https://example.moodle.net',
'host' => 'example.moodle.net',
'path' => null,
'exception' => false,
],
'Slash path' => [
'url' => 'https://example.moodle.net/',
'host' => 'example.moodle.net',
'path' => '/',
'exception' => false,
],
'Path includes file and extension' => [
'url' => 'https://example.moodle.net/uploads/123456789/pic.png',
'host' => 'example.moodle.net',
'path' => '/uploads/123456789/pic.png',
'exception' => false,
],
'Path includes file, extension and params' => [
'url' => 'https://example.moodle.net/uploads/123456789/pic.png?option=1&option2=test',
'host' => 'example.moodle.net',
'path' => '/uploads/123456789/pic.png',
'exception' => false,
],
'Malformed - invalid' => [
'url' => 'invalid',
'host' => null,
'path' => null,
'exception' => true,
],
'Direct, non-encoded utf8 - invalid' => [
'url' => 'http://москва.рф/services/',
'host' => 'москва.рф',
'path' => '/services/',
'exception' => true,
],
];
}
}

View File

@ -0,0 +1,30 @@
<?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/>.
/**
* Version file for tool_moodlenet
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'tool_moodlenet';
$plugin->version = 2020060500;
$plugin->requires = 2020022800.01;
$plugin->maturity = MATURITY_ALPHA;

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 modal-footer">
{{>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

@ -35,6 +35,7 @@ $string['filterrowlegend'] = 'Filter {$a}';
$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';
@ -89,6 +90,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');

View File

@ -1631,6 +1631,18 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
}
}
.modchooser .modal-footer {
height: 70px;
background: $modal-content-bg;
.moodlenet-logo {
.icon {
height: 2.5rem;
width: 6rem;
margin-bottom: .6rem;
}
}
}
.modchoosercontainer.noscroll {
overflow-y: hidden;
}
@ -1678,7 +1690,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;
@ -2441,6 +2453,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 */
}

View File

@ -10993,6 +10993,14 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.modchooser .modal-body .carousel-item .loading-icon .icon {
margin: 1em auto; }
.modchooser .modal-footer {
height: 70px;
background: #fff; }
.modchooser .modal-footer .moodlenet-logo .icon {
height: 2.5rem;
width: 6rem;
margin-bottom: .6rem; }
.modchoosercontainer.noscroll {
overflow-y: hidden; }
@ -11030,7 +11038,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 {
@ -11623,6 +11631,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

@ -11200,6 +11200,14 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.modchooser .modal-body .carousel-item .loading-icon .icon {
margin: 1em auto; }
.modchooser .modal-footer {
height: 70px;
background: #fff; }
.modchooser .modal-footer .moodlenet-logo .icon {
height: 2.5rem;
width: 6rem;
margin-bottom: .6rem; }
.modchoosercontainer.noscroll {
overflow-y: hidden; }
@ -11237,7 +11245,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 {
@ -11834,6 +11842,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 = 2020060400.00; // 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