--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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
+ };
+});
--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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,
+ };
+});
--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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"]',
+ },
+ };
+});
--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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,
+ };
+});
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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'),
+ ]))
+ ]);
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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;
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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;
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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];
+ }
+}
+
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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]);
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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();
+ }
+}
+
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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;
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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);
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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;
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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();
+ }
+
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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;
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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;
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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);
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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'),
+ ];
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+/**
+ * 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';
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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'),
+ ];
+ }
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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]
+ ],
+];
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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;
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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');
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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();
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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.';
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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
+ );
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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();
--- /dev/null
+<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>
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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();
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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);
+}
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template 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}}
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template 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>
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template 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>
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template 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>
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template 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}}> <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>
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template 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>
--- /dev/null
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+ @template 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 }}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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],
+
+ ];
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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());
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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);
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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());
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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§ion=2'
+ ],
+ [
+ '@profile@name@domain.name',
+ 1,
+ 2,
+ 'https://domain.name/' . MOODLENET_DEFAULT_ENDPOINT . '?site=' . urlencode($CFG->wwwroot)
+ . '&course=1§ion=2'
+ ],
+ [
+ 'https://domain.name',
+ 1,
+ 2,
+ 'https://domain.name/' . MOODLENET_DEFAULT_ENDPOINT . '?site=' . urlencode($CFG->wwwroot)
+ . '&course=1§ion=2'
+ ]
+ ];
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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);
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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();
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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,
+ ],
+ ];
+ }
+}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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;
</div>
</div>
{{^showFooter}}
- <div class="fixed-bottom position-absolute py-3 px-4 border-top">
+ <div class="fixed-bottom position-absolute py-3 px-4 border-top modal-footer">
{{>core_course/local/activitychooser/footer_partial}}
</div>
{{/showFooter}}
.modchooser .modal-footer {
height: 70px;
+ background: $modal-content-bg;
.moodlenet-logo {
.icon {
height: 2.5rem;
margin: 1em auto; }
.modchooser .modal-footer {
- height: 70px; }
+ height: 70px;
+ background: #fff; }
.modchooser .modal-footer .moodlenet-logo .icon {
height: 2.5rem;
width: 6rem;
margin: 1em auto; }
.modchooser .modal-footer {
- height: 70px; }
+ height: 70px;
+ background: #fff; }
.modchooser .modal-footer .moodlenet-logo .icon {
height: 2.5rem;
width: 6rem;