first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+189
View File
@@ -0,0 +1,189 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This is the external API for this component.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/filelib.php');
require_once(__DIR__ . '/../lib.php');
use core_course\external\course_summary_exporter;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
/**
* 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_NOTAGS, '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'),
]))
]);
}
}
@@ -0,0 +1,194 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the import_backup_helper class.
*
* @package tool_moodlenet
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_backup_helper class.
*
* The import_backup_helper objects provide a means to prepare a backup for for restoration of a course or activity backup file.
*
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_backup_helper {
/** @var remote_resource $remoteresource A file resource to be restored. */
protected $remoteresource;
/** @var user $user The user trying to restore a file. */
protected $user;
/** @var context $context The context we are trying to restore this file into. */
protected $context;
/** @var int $useruploadlimit The size limit that this user can upload in this context. */
protected $useruploadlimit;
/**
* Constructor for the import backup helper.
*
* @param remote_resource $remoteresource A remote file resource
* @param \stdClass $user The user importing a file.
* @param \context $context Context to restore into.
*/
public function __construct(remote_resource $remoteresource, \stdClass $user, \context $context) {
$this->remoteresource = $remoteresource;
$this->user = $user;
$this->context = $context;
$maxbytes = 0;
if ($this->context->contextlevel == CONTEXT_COURSE) {
$course = get_course($this->context->instanceid);
$maxbytes = $course->maxbytes;
}
$this->useruploadlimit = get_user_max_upload_file_size($this->context, get_config('core', 'maxbytes'),
$maxbytes, 0, $this->user);
}
/**
* Return a stored user draft file for processing.
*
* @return \stored_file The imported file to ultimately be restored.
*/
public function get_stored_file(): \stored_file {
// Check if the user can upload a backup to this context.
require_capability('moodle/restore:uploadfile', $this->context, $this->user->id);
// Before starting a potentially lengthy download, try to ensure the file size does not exceed the upload size restrictions
// for the user. This is a time saving measure.
// This is a naive check, that serves only to catch files if they provide the content length header.
// Because of potential content encoding (compression), the stored file will be checked again after download as well.
$size = $this->remoteresource->get_download_size() ?? -1;
if ($this->size_exceeds_upload_limit($size)) {
throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $size,
'uploadlimit' => $this->useruploadlimit]);
}
[$filepath, $filename] = $this->remoteresource->download_to_requestdir();
\core\antivirus\manager::scan_file($filepath, $filename, true);
// Check the final size of file against the user upload limits.
$localsize = filesize(sprintf('%s/%s', $filepath, $filename));
if ($this->size_exceeds_upload_limit($localsize)) {
throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $localsize,
'uploadlimit' => $this->useruploadlimit]);
}
return $this->create_user_draft_stored_file($filename, $filepath);
}
/**
* Does the size exceed the upload limit for the current import, taking into account user and core settings.
*
* @param int $sizeinbytes
* @return bool true if exceeded, false otherwise.
*/
protected function size_exceeds_upload_limit(int $sizeinbytes): bool {
$maxbytes = 0;
if ($this->context->contextlevel == CONTEXT_COURSE) {
$course = get_course($this->context->instanceid);
$maxbytes = $course->maxbytes;
}
$maxbytes = get_user_max_upload_file_size($this->context, get_config('core', 'maxbytes'), $maxbytes, 0,
$this->user);
if ($maxbytes != USER_CAN_IGNORE_FILE_SIZE_LIMITS && $sizeinbytes > $maxbytes) {
return true;
}
return false;
}
/**
* Create a file in the user drafts ready for use by plugins implementing dndupload_handle().
*
* @param string $filename the name of the file on disk
* @param string $path the path where the file is stored on disk
* @return \stored_file
*/
protected function create_user_draft_stored_file(string $filename, string $path): \stored_file {
global $CFG;
$record = new \stdClass();
$record->filearea = 'draft';
$record->component = 'user';
$record->filepath = '/';
$record->itemid = file_get_unused_draft_itemid();
$record->license = $CFG->sitedefaultlicense;
$record->author = '';
$record->filename = clean_param($filename, PARAM_FILE);
$record->contextid = \context_user::instance($this->user->id)->id;
$record->userid = $this->user->id;
$fullpathwithname = sprintf('%s/%s', $path, $filename);
$fs = get_file_storage();
return $fs->create_file_from_pathname($record, $fullpathwithname);
}
/**
* Looks for a context that this user has permission to upload backup files to.
* This gets a list of roles that the user has, checks for the restore:uploadfile capability and then sends back a context
* that has this permission if available.
*
* This starts with the highest context level and moves down i.e. system -> category -> course.
*
* @param int $userid The user ID that we are looking for a working context for.
* @return \context A context that allows the upload of backup files.
*/
public static function get_context_for_user(int $userid): ?\context {
global $DB;
if (is_siteadmin()) {
return \context_system::instance();
}
$sql = "SELECT ctx.id, ctx.contextlevel, ctx.instanceid, ctx.path, ctx.depth, ctx.locked
FROM {context} ctx
JOIN {role_assignments} r ON ctx.id = r.contextid
WHERE r.userid = :userid AND ctx.contextlevel IN (:contextsystem, :contextcategory, :contextcourse)
ORDER BY ctx.contextlevel ASC";
$params = [
'userid' => $userid,
'contextsystem' => CONTEXT_SYSTEM,
'contextcategory' => CONTEXT_COURSECAT,
'contextcourse' => CONTEXT_COURSE
];
$records = $DB->get_records_sql($sql, $params);
foreach ($records as $record) {
\context_helper::preload_from_record($record);
if ($record->contextlevel == CONTEXT_COURSECAT) {
$context = \context_coursecat::instance($record->instanceid);
} else if ($record->contextlevel == CONTEXT_COURSE) {
$context = \context_course::instance($record->instanceid);
} else {
$context = \context_system::instance();
}
if (has_capability('moodle/restore:uploadfile', $context, $userid)) {
return $context;
}
}
return null;
}
}
@@ -0,0 +1,91 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the import_handler_info class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_handler_info class.
*
* An import_handler_info object represent an resource import handler for a particular module.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_handler_info {
/** @var string $modulename the name of the module. */
protected $modulename;
/** @var string $description the description. */
protected $description;
/** @var import_strategy $importstrategy the strategy which will be used to import resources handled by this handler */
protected $importstrategy;
/**
* The import_handler_info constructor.
*
* @param string $modulename the name of the module handling the file extension. E.g. 'label'.
* @param string $description A description of how the module handles files of this extension type.
* @param import_strategy $strategy the strategy which will be used to import the resource.
* @throws \coding_exception
*/
public function __construct(string $modulename, string $description, import_strategy $strategy) {
if (empty($modulename)) {
throw new \coding_exception("Module name cannot be empty.");
}
if (empty($description)) {
throw new \coding_exception("Description cannot be empty.");
}
$this->modulename = $modulename;
$this->description = $description;
$this->importstrategy = $strategy;
}
/**
* Get the name of the module.
*
* @return string the module name, e.g. 'label'.
*/
public function get_module_name(): string {
return $this->modulename;
}
/**
* Get a human readable, localised description of how the file is handled by the module.
*
* @return string the localised description.
*/
public function get_description(): string {
return $this->description;
}
/**
* Get the import strategy used by this handler.
*
* @return import_strategy the import strategy object.
*/
public function get_strategy(): import_strategy {
return $this->importstrategy;
}
}
@@ -0,0 +1,188 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the import_handler_registry class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_handler_registry class.
*
* The import_handler_registry objects represent a register of modules handling various file extensions for a given course and user.
* Only modules which are available to the user in the course are included in the register for that user.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_handler_registry {
/**
* @var array array containing the names and messages of all modules handling import of resources as a 'file' type.
*/
protected $filehandlers = [];
/**
* @var array $typehandlers the array of modules registering as handlers of other, non-file types, indexed by typename.
*/
protected $typehandlers = [];
/**
* @var array $registry the aggregate of all registrations made by plugins, indexed by 'file' and 'type'.
*/
protected $registry = [];
/**
* @var \context_course the course context object.
*/
protected $context;
/**
* @var \stdClass a course object.
*/
protected $course;
/**
* @var \stdClass a user object.
*/
protected $user;
/**
* The import_handler_registry constructor.
*
* @param \stdClass $course the course, which impacts available handlers.
* @param \stdClass $user the user, which impacts available handlers.
*/
public function __construct(\stdClass $course, \stdClass $user) {
$this->course = $course;
$this->user = $user;
$this->context = \context_course::instance($course->id);
// Generate the full list of handlers for all extensions for this user and course.
$this->populate_handlers();
}
/**
* Get all handlers for the remote resource, depending on the strategy being used to import the resource.
*
* @param remote_resource $resource the remote resource.
* @param import_strategy $strategy an import_strategy instance.
* @return import_handler_info[] the array of import_handler_info handlers.
*/
public function get_resource_handlers_for_strategy(remote_resource $resource, import_strategy $strategy): array {
return $strategy->get_handlers($this->registry, $resource);
}
/**
* Get a specific handler for the resource, belonging to a specific module and for a specific strategy.
*
* @param remote_resource $resource the remote resource.
* @param string $modname the name of the module, e.g. 'label'.
* @param import_strategy $strategy a string representing how to treat the resource. e.g. 'file', 'link'.
* @return import_handler_info|null the import_handler_info object, if found, otherwise null.
*/
public function get_resource_handler_for_mod_and_strategy(remote_resource $resource, string $modname,
import_strategy $strategy): ?import_handler_info {
foreach ($strategy->get_handlers($this->registry, $resource) as $handler) {
if ($handler->get_module_name() === $modname) {
return $handler;
}
}
return null;
}
/**
* Build up a list of extension handlers by leveraging the dndupload_register callbacks.
*/
protected function populate_handlers() {
// Generate a dndupload_handler object, just so we can call ->is_known_type() on the types being registered by plugins.
// We must vet each type which is reported to be handled against the list of known, supported types.
global $CFG;
require_once($CFG->dirroot . '/course/dnduploadlib.php');
$dndhandlers = new \dndupload_handler($this->course);
// Get the list of mods enabled at site level first. We need to cross check this.
$pluginman = \core_plugin_manager::instance();
$sitemods = $pluginman->get_plugins_of_type('mod');
$sitedisabledmods = array_filter($sitemods, function(\core\plugininfo\mod $modplugininfo){
return !$modplugininfo->is_enabled();
});
$sitedisabledmods = array_map(function($modplugininfo) {
return $modplugininfo->name;
}, $sitedisabledmods);
// Loop through all modules to find the registered handlers.
$mods = get_plugin_list_with_function('mod', 'dndupload_register');
foreach ($mods as $component => $funcname) {
list($modtype, $modname) = \core_component::normalize_component($component);
if (!empty($sitedisabledmods) && array_key_exists($modname, $sitedisabledmods)) {
continue; // Module is disabled at the site level.
}
if (!course_allowed_module($this->course, $modname, $this->user)) {
continue; // User does not have permission to add this module to the course.
}
if (!$resp = component_callback($component, 'dndupload_register')) {
continue;
};
if (isset($resp['files'])) {
foreach ($resp['files'] as $file) {
$this->register_file_handler($file['extension'], $modname, $file['message']);
}
}
if (isset($resp['types'])) {
foreach ($resp['types'] as $type) {
if (!$dndhandlers->is_known_type($type['identifier'])) {
throw new \coding_exception("Trying to add handler for unknown type $type");
}
$this->register_type_handler($type['identifier'], $modname, $type['message']);
}
}
}
$this->registry = [
'files' => $this->filehandlers,
'types' => $this->typehandlers
];
}
/**
* Adds a type handler to the list.
*
* @param string $identifier the name of the type.
* @param string $module the name of the module, e.g. 'label'.
* @param string $message the message describing how the module handles the type.
*/
protected function register_type_handler(string $identifier, string $module, string $message) {
$this->typehandlers[$identifier][] = ['module' => $module, 'message' => $message];
}
/**
* Adds a file extension handler to the list.
*
* @param string $extension the extension, e.g. 'png'.
* @param string $module the name of the module handling this extension
* @param string $message the message describing how the module handles the extension.
*/
protected function register_file_handler(string $extension, string $module, string $message) {
$extension = strtolower($extension);
$this->filehandlers[$extension][] = ['module' => $module, 'message' => $message];
}
}
@@ -0,0 +1,126 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the import_info class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* Class import_info, describing objects which represent a resource being imported by a user.
*
* Objects of this class encapsulate both:
* - information about the resource (remote_resource).
* - config data pertaining to the import process, such as the destination course and section
* and how the resource should be treated (i.e. the type and the name of the module selected as the import handler)
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_info {
/** @var int $userid the user conducting this import. */
protected $userid;
/** @var remote_resource $resource the resource being imported. */
protected $resource;
/** @var \stdClass $config config data pertaining to the import process, e.g. course, section, type. */
protected $config;
/** @var string $id string identifier for this object. */
protected $id;
/**
* The import_controller constructor.
*
* @param int $userid the id of the user performing the import.
* @param remote_resource $resource the resource being imported.
* @param \stdClass $config import config like 'course', 'section', 'type'.
*/
public function __construct(int $userid, remote_resource $resource, \stdClass $config) {
$this->userid = $userid;
$this->resource = $resource;
$this->config = $config;
$this->id = md5($resource->get_url()->get_value());
}
/**
* Get the id of this object.
*/
public function get_id() {
return $this->id;
}
/**
* Get the remote resource being imported.
*
* @return remote_resource the remote resource being imported.
*/
public function get_resource(): remote_resource {
return $this->resource;
}
/**
* Get the configuration data pertaining to the import.
*
* @return \stdClass the import configuration data.
*/
public function get_config(): \stdClass {
return $this->config;
}
/**
* Set the configuration data pertaining to the import.
*
* @param \stdClass $config the configuration data to set.
*/
public function set_config(\stdClass $config): void {
$this->config = $config;
}
/**
* Get an import_info object by id.
*
* @param string $id the id of the import_info object to load.
* @return mixed an import_info object if found, otherwise null.
*/
public static function load(string $id): ?import_info {
// This currently lives in the session, so we don't need userid.
// It might be useful if we ever move to another storage mechanism however, where we would need it.
global $SESSION;
return isset($SESSION->moodlenetimports[$id]) ? unserialize($SESSION->moodlenetimports[$id]) : null;
}
/**
* Save this object to a store which is accessible across requests.
*/
public function save(): void {
global $SESSION;
$SESSION->moodlenetimports[$this->id] = serialize($this);
}
/**
* Remove all information about an import from the store.
*/
public function purge(): void {
global $SESSION;
unset($SESSION->moodlenetimports[$this->id]);
}
}
@@ -0,0 +1,206 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the import_processor class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_processor class.
*
* The import_processor objects provide a means to import a remote resource into a course section, delegating the handling of
* content to the relevant module, via its dndupload_handler callback.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_processor {
/** @var object The course that we are uploading to */
protected $course = null;
/** @var int The section number we are uploading to */
protected $section = null;
/** @var import_handler_registry $handlerregistry registry object to use for cross checking the supplied handler.*/
protected $handlerregistry;
/** @var import_handler_info $handlerinfo information about the module handling the import.*/
protected $handlerinfo;
/** @var \stdClass $user the user conducting the import.*/
protected $user;
/** @var remote_resource $remoteresource the remote resource being imported.*/
protected $remoteresource;
/** @var string[] $descriptionoverrides list of modules which support having their descriptions updated, post-import. */
protected $descriptionoverrides = ['folder', 'page', 'resource', 'scorm', 'url'];
/**
* The import_processor constructor.
*
* @param \stdClass $course the course object.
* @param int $section the section number in the course, starting at 0.
* @param remote_resource $remoteresource the remote resource to import.
* @param import_handler_info $handlerinfo information about which module is handling the import.
* @param import_handler_registry $handlerregistry A registry of import handlers, to use for validation.
* @throws \coding_exception If any of the params are invalid.
*/
public function __construct(\stdClass $course, int $section, remote_resource $remoteresource, import_handler_info $handlerinfo,
import_handler_registry $handlerregistry) {
global $DB, $USER;
if ($section < 0) {
throw new \coding_exception("Invalid section number $section. Must be > 0.");
}
if (!$DB->record_exists('modules', array('name' => $handlerinfo->get_module_name()))) {
throw new \coding_exception("Module {$handlerinfo->get_module_name()} does not exist");
}
$this->course = $course;
$this->section = $section;
$this->handlerregistry = $handlerregistry;
$this->user = $USER;
$this->remoteresource = $remoteresource;
$this->handlerinfo = $handlerinfo;
// ALL handlers must have a strategy and ANY strategy can process ANY resource.
// It is therefore NOT POSSIBLE to have a resource that CANNOT be processed by a handler.
// So, there's no need to verify that the remote_resource CAN be handled by the handler. It always can.
}
/**
* Run the import process, including file download, module creation and cleanup (cache purge, etc).
*/
public function process(): void {
// Allow the strategy to do setup for this file import.
$moduledata = $this->handlerinfo->get_strategy()->import($this->remoteresource, $this->user, $this->course, $this->section);
// Create the course module, and add that information to the data to be sent to the plugin handling the resource.
$cmdata = $this->create_course_module($this->course, $this->section, $this->handlerinfo->get_module_name());
$moduledata->coursemodule = $cmdata->id;
// Now, send the data to the handling plugin to let it set up.
$instanceid = plugin_callback('mod', $this->handlerinfo->get_module_name(), 'dndupload', 'handle', [$moduledata],
'invalidfunction');
if ($instanceid == 'invalidfunction') {
$name = $this->handlerinfo->get_module_name();
throw new \coding_exception("$name does not support drag and drop upload (missing {$name}_dndupload_handle function)");
}
// Now, update the module description if the module supports it and only if it's not currently set.
$this->update_module_description($instanceid);
// Finish setting up the course module.
$this->finish_setup_course_module($instanceid, $cmdata->id);
}
/**
* Update the module's description (intro), if that feature is supported.
*
* @param int $instanceid the instance id of the module to update.
*/
protected function update_module_description(int $instanceid): void {
global $DB, $CFG;
require_once($CFG->libdir . '/moodlelib.php');
if (plugin_supports('mod', $this->handlerinfo->get_module_name(), FEATURE_MOD_INTRO, true)) {
require_once($CFG->libdir . '/editorlib.php');
require_once($CFG->libdir . '/modinfolib.php');
$rec = $DB->get_record($this->handlerinfo->get_module_name(), ['id' => $instanceid]);
if (empty($rec->intro) || in_array($this->handlerinfo->get_module_name(), $this->descriptionoverrides)) {
$updatedata = (object)[
'id' => $instanceid,
'intro' => clean_param($this->remoteresource->get_description(), PARAM_TEXT),
'introformat' => editors_get_preferred_format()
];
$DB->update_record($this->handlerinfo->get_module_name(), $updatedata);
rebuild_course_cache($this->course->id, true);
}
}
}
/**
* Create the course module to hold the file/content that has been uploaded.
* @param \stdClass $course the course object.
* @param int $section the section.
* @param string $modname the name of the module, e.g. 'label'.
* @return \stdClass the course module data.
*/
protected function create_course_module(\stdClass $course, int $section, string $modname): \stdClass {
global $CFG;
require_once($CFG->dirroot . '/course/modlib.php');
list($module, $context, $cw, $cm, $data) = prepare_new_moduleinfo_data($course, $modname, $section);
$data->visible = false; // The module is created in a hidden state.
$data->coursemodule = $data->id = add_course_module($data);
return $data;
}
/**
* Finish off any course module setup, such as adding to the course section and firing events.
*
* @param int $instanceid id returned by the mod when it was created.
* @param int $cmid the course module record id, for removal if something went wrong.
*/
protected function finish_setup_course_module($instanceid, int $cmid): void {
global $DB;
if (!$instanceid) {
// Something has gone wrong - undo everything we can.
course_delete_module($cmid);
throw new \moodle_exception('errorcreatingactivity', 'moodle', '', $this->handlerinfo->get_module_name());
}
// Note the section visibility.
$visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible;
$DB->set_field('course_modules', 'instance', $instanceid, array('id' => $cmid));
// Rebuild the course cache after update action.
rebuild_course_cache($this->course->id, true);
course_add_cm_to_section($this->course, $cmid, $this->section);
set_coursemodule_visible($cmid, $visible);
if (!$visible) {
$DB->set_field('course_modules', 'visibleold', 1, array('id' => $cmid));
}
// Retrieve the final info about this module.
$info = get_fast_modinfo($this->course, $this->user->id);
if (!isset($info->cms[$cmid])) {
// The course module has not been properly created in the course - undo everything.
course_delete_module($cmid);
throw new \moodle_exception('errorcreatingactivity', 'moodle', '', $this->handlerinfo->get_module_name());
}
$mod = $info->get_cm($cmid);
// Trigger course module created event.
$event = \core\event\course_module_created::create_from_cm($mod);
$event->trigger();
}
}
@@ -0,0 +1,69 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the import_strategy interface.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_strategy interface.
*
* This provides a contract allowing different import strategies to be implemented.
*
* An import_strategy encapsulates the logic used to prepare a remote_resource for import into Moodle in some way and is used by the
* import_processor (to perform aforementioned preparations) before it hands control of the import over to a course module plugin.
*
* We may wish to have many strategies because the preparation steps may vary depending on how the resource is to be treated.
* E.g. We may wish to import as a file in which case download steps will be required, or we may simply wish to import the remote
* resource as a link, in which cases setup steps will not require any file download.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface import_strategy {
/**
* Get an array of import_handler_info objects supported by this import strategy, based on the registrydata and resource.
*
* Implementations should check the registry data for any entries which align with their import strategy and should create
* import_handler_info objects to represent each relevant entry. If an entry represents a module, or handling type which does
* not align with the strategy, that item should simply be skipped.
*
* E.g. If one strategy aims to import all remote resources as files (e.g. import_strategy_file), it would only generate a list
* of import_handler_info objects created from those registry entries of type 'file', as those entries represent the modules
* which have said they can handle resources as files.
*
* @param array $registrydata The fully populated handler registry.
* @param remote_resource $resource the remote resource.
* @return import_handler_info[] the array of import_handler_info objects, or an empty array if none were matched.
*/
public function get_handlers(array $registrydata, remote_resource $resource): array;
/**
* Called during import to perform required import setup steps.
*
* @param remote_resource $resource the resource to import.
* @param \stdClass $user the user to import on behalf of.
* @param \stdClass $course the course into which the remote resource is being imported.
* @param int $section the section into which the remote resource is being imported.
* @return \stdClass the module data which will be passed on to the course module plugin.
*/
public function import(remote_resource $resource, \stdClass $user, \stdClass $course, int $section): \stdClass;
}
@@ -0,0 +1,170 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the import_strategy_file class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
use core\antivirus\manager as avmanager;
/**
* The import_strategy_file class.
*
* The import_strategy_file objects contains the setup steps needed to prepare a resource for import as a file into Moodle. This
* ensures the remote_resource is first downloaded and put in a draft file area, ready for use as a file by the handling module.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_strategy_file implements import_strategy {
/**
* Get an array of import_handler_info objects representing modules supporting import of this file type.
*
* @param array $registrydata the fully populated registry.
* @param remote_resource $resource the remote resource.
* @return import_handler_info[] the array of import_handler_info objects.
*/
public function get_handlers(array $registrydata, remote_resource $resource): array {
$handlers = [];
foreach ($registrydata['files'] as $index => $items) {
foreach ($items as $item) {
if ($index === $resource->get_extension() || $index === '*') {
$handlers[] = new import_handler_info($item['module'], $item['message'], $this);
}
}
}
return $handlers;
}
/**
* Import the remote resource according to the rules of this strategy.
*
* @param remote_resource $resource the resource to import.
* @param \stdClass $user the user to import on behalf of.
* @param \stdClass $course the course into which the remote_resource is being imported.
* @param int $section the section into which the remote_resource is being imported.
* @return \stdClass the module data.
* @throws \moodle_exception if the file size means the upload limit is exceeded for the user.
*/
public function import(remote_resource $resource, \stdClass $user, \stdClass $course, int $section): \stdClass {
// Before starting a potentially lengthy download, try to ensure the file size does not exceed the upload size restrictions
// for the user. This is a time saving measure.
// This is a naive check, that serves only to catch files if they provide the content length header.
// Because of potential content encoding (compression), the stored file will be checked again after download as well.
$size = $resource->get_download_size() ?? -1;
$useruploadlimit = $this->get_user_upload_limit($user, $course);
if ($this->size_exceeds_upload_limit($size, $useruploadlimit)) {
throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $size,
'uploadlimit' => $useruploadlimit]);
}
// Download the file into a request directory and scan it.
[$filepath, $filename] = $resource->download_to_requestdir();
avmanager::scan_file($filepath, $filename, true);
// Check the final size of file against the user upload limits.
$localsize = filesize(sprintf('%s/%s', $filepath, $filename));
if ($this->size_exceeds_upload_limit($localsize, $useruploadlimit)) {
throw new \moodle_exception('uploadlimitexceeded', 'tool_moodlenet', '', ['filesize' => $localsize,
'uploadlimit' => $useruploadlimit]);
}
// Store in the user draft file area.
$storedfile = $this->create_user_draft_stored_file($user, $filename, $filepath);
// Prepare the data to be sent to the modules dndupload_handle hook.
return $this->prepare_module_data($course, $resource, $storedfile->get_itemid());
}
/**
* Creates the data to pass to the dndupload_handle() hooks.
*
* @param \stdClass $course the course record.
* @param remote_resource $resource the resource being imported as a file.
* @param int $draftitemid the itemid of the draft file.
* @return \stdClass the data object.
*/
protected function prepare_module_data(\stdClass $course, remote_resource $resource, int $draftitemid): \stdClass {
$data = new \stdClass();
$data->type = 'Files';
$data->course = $course;
$data->draftitemid = $draftitemid;
$data->displayname = $resource->get_name();
return $data;
}
/**
* Get the max file size limit for the user in the course.
*
* @param \stdClass $user the user to check.
* @param \stdClass $course the course to check in.
* @return int the file size limit, in bytes.
*/
protected function get_user_upload_limit(\stdClass $user, \stdClass $course): int {
return get_user_max_upload_file_size(\context_course::instance($course->id), get_config('core', 'maxbytes'),
$course->maxbytes, 0, $user);
}
/**
* Does the size exceed the upload limit for the current import, taking into account user and core settings.
*
* @param int $sizeinbytes the size, in bytes.
* @param int $useruploadlimit the upload limit, in bytes.
* @return bool true if exceeded, false otherwise.
* @throws \dml_exception
*/
protected function size_exceeds_upload_limit(int $sizeinbytes, int $useruploadlimit): bool {
if ($useruploadlimit != USER_CAN_IGNORE_FILE_SIZE_LIMITS && $sizeinbytes > $useruploadlimit) {
return true;
}
return false;
}
/**
* Create a file in the user drafts ready for use by plugins implementing dndupload_handle().
*
* @param \stdClass $user the user object.
* @param string $filename the name of the file on disk
* @param string $path the path where the file is stored on disk
* @return \stored_file
*/
protected function create_user_draft_stored_file(\stdClass $user, string $filename, string $path): \stored_file {
global $CFG;
$record = new \stdClass();
$record->filearea = 'draft';
$record->component = 'user';
$record->filepath = '/';
$record->itemid = file_get_unused_draft_itemid();
$record->license = $CFG->sitedefaultlicense;
$record->author = '';
$record->filename = clean_param($filename, PARAM_FILE);
$record->contextid = \context_user::instance($user->id)->id;
$record->userid = $user->id;
$fullpathwithname = sprintf('%s/%s', $path, $filename);
$fs = get_file_storage();
return $fs->create_file_from_pathname($record, $fullpathwithname);
}
}
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the import_strategy_link class.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The import_strategy_link class.
*
* The import_strategy_link objects contains the setup steps needed to prepare a resource for import as a URL into Moodle.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_strategy_link implements import_strategy {
/**
* Get an array of import_handler_info objects representing modules supporting import of the resource.
*
* @param array $registrydata the fully populated registry.
* @param remote_resource $resource the remote resource.
* @return import_handler_info[] the array of import_handler_info objects.
*/
public function get_handlers(array $registrydata, remote_resource $resource): array {
$handlers = [];
foreach ($registrydata['types'] as $identifier => $items) {
foreach ($items as $item) {
if ($identifier === 'url') {
$handlers[] = new import_handler_info($item['module'], $item['message'], $this);
}
}
}
return $handlers;
}
/**
* Import the remote resource according to the rules of this strategy.
*
* @param remote_resource $resource the resource to import.
* @param \stdClass $user the user to import on behalf of.
* @param \stdClass $course the course into which the remote_resource is being imported.
* @param int $section the section into which the remote_resource is being imported.
* @return \stdClass the module data.
*/
public function import(remote_resource $resource, \stdClass $user, \stdClass $course, int $section): \stdClass {
$data = new \stdClass();
$data->type = 'url';
$data->course = $course;
$data->content = $resource->get_url()->get_value();
$data->displayname = $resource->get_name();
return $data;
}
}
@@ -0,0 +1,169 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the remote_resource class definition.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The remote_resource class.
*
* Objects of type remote_resource provide a means of interacting with resources over HTTP.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class remote_resource {
/** @var \curl $curl the curl http helper.*/
protected $curl;
/** @var url $url the url to the remote resource.*/
protected $url;
/** @var string $filename the name of this remote file.*/
protected $filename;
/** @var string $extension the file extension of this remote file.*/
protected $extension;
/** @var array $headinfo the array of information for the most recent HEAD request.*/
protected $headinfo = [];
/** @var \stdClass $metadata information about the resource. */
protected $metadata;
/**
* The remote_resource constructor.
*
* @param \curl $curl a curl object for HTTP requests.
* @param url $url the URL of the remote resource.
* @param \stdClass $metadata resource metadata such as name, summary, license, etc.
*/
public function __construct(\curl $curl, url $url, \stdClass $metadata) {
$this->curl = $curl;
$this->url = $url;
$this->filename = pathinfo($this->url->get_path() ?? '', PATHINFO_FILENAME);
$this->extension = pathinfo($this->url->get_path() ?? '', PATHINFO_EXTENSION);
$this->metadata = $metadata;
}
/**
* Return the URL for this remote resource.
*
* @return url the url object.
*/
public function get_url(): url {
return $this->url;
}
/**
* Get the name of the file as taken from the metadata.
*/
public function get_name(): string {
return $this->metadata->name ?? '';
}
/**
* Get the resource metadata.
*
* @return \stdClass the metadata.
*/
public function get_metadata(): \stdClass {
return$this->metadata;
}
/**
* Get the description of the resource as taken from the metadata.
*
* @return string
*/
public function get_description(): string {
return $this->metadata->description ?? '';
}
/**
* Return the extension of the file, if found.
*
* @return string the extension of the file, if found.
*/
public function get_extension(): string {
return $this->extension;
}
/**
* Returns the file size of the remote file, in bytes, or null if it cannot be determined.
*
* @return int|null the content length, if able to be determined, otherwise null.
*/
public function get_download_size(): ?int {
$this->get_resource_info();
return $this->headinfo['download_content_length'] ?? null;
}
/**
* Download the remote resource to a local requestdir, returning the path and name of the resulting file.
*
* @return array an array containing filepath adn filename, e.g. [filepath, filename].
* @throws \moodle_exception if the file cannot be downloaded.
*/
public function download_to_requestdir(): array {
$filename = sprintf('%s.%s', $this->filename, $this->get_extension());
$path = make_request_directory();
$fullpathwithname = sprintf('%s/%s', $path, $filename);
// In future, use a timeout (download and/or connection) controlled by a tool_moodlenet setting.
$downloadtimeout = 30;
$result = $this->curl->download_one($this->url->get_value(), null, ['filepath' => $fullpathwithname,
'timeout' => $downloadtimeout]);
if ($result !== true) {
throw new \moodle_exception('errorduringdownload', 'tool_moodlenet', '', $result);
}
return [$path, $filename];
}
/**
* Fetches information about the remote resource via a HEAD request.
*
* @throws \coding_exception if any connection problems occur.
*/
protected function get_resource_info() {
if (!empty($this->headinfo)) {
return;
}
$options['CURLOPT_RETURNTRANSFER'] = 1;
$options['CURLOPT_FOLLOWLOCATION'] = 1;
$options['CURLOPT_MAXREDIRS'] = 5;
$options['CURLOPT_FAILONERROR'] = 1; // We want to consider http error codes as errors to report, not just status codes.
$this->curl->head($this->url->get_value(), $options);
$errorno = $this->curl->get_errno();
$this->curl->resetopt();
if ($errorno !== 0) {
$message = 'Problem during HEAD request for remote resource \''.$this->url->get_value().'\'. Curl Errno: ' . $errorno;
throw new \coding_exception($message);
}
$this->headinfo = $this->curl->get_info();
}
}
@@ -0,0 +1,84 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the url class, providing a representation of a url and operations on its component parts.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\local;
/**
* The url class, providing a representation of a url and operations on its component parts.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class url {
/** @var string $url the full URL string.*/
protected $url;
/** @var string|null $path the path component of this URL.*/
protected $path;
/** @var host|null $host the host component of this URL.*/
protected $host;
/**
* The url constructor.
*
* @param string $url the URL string.
* @throws \coding_exception if the URL does not pass syntax validation.
*/
public function __construct(string $url) {
// This object supports URLs as per the spec, so non-ascii chars must be encoded as per IDNA rules.
if (!filter_var($url, FILTER_VALIDATE_URL)) {
throw new \coding_exception('Malformed URL');
}
$this->url = $url;
$this->path = parse_url($url, PHP_URL_PATH);
$this->host = parse_url($url, PHP_URL_HOST);
}
/**
* Get the path component of the URL.
*
* @return string|null the path component of the URL.
*/
public function get_path(): ?string {
return $this->path;
}
/**
* Return the domain component of the URL.
*
* @return string|null the domain component of the URL.
*/
public function get_host(): ?string {
return $this->host;
}
/**
* Return the full URL string.
*
* @return string the full URL string.
*/
public function get_value() {
return $this->url;
}
}
@@ -0,0 +1,107 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Moodle net user profile class.
*
* @package tool_moodlenet
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet;
/**
* A class to represent the moodlenet profile.
*
* @package tool_moodlenet
* @copyright 2020 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_user_profile {
/** @var string $profile The full profile name. */
protected $profile;
/** @var int $userid The user ID that this profile belongs to. */
protected $userid;
/** @var string $username The username from $userprofile */
protected $username;
/** @var string $domain The domain from $domain */
protected $domain;
/**
* Constructor method.
*
* @param string $userprofile The moodle net user profile string.
* @param int $userid The user ID that this profile belongs to.
*/
public function __construct(string $userprofile, int $userid) {
$this->profile = $userprofile;
$this->userid = $userid;
$explodedprofile = explode('@', $this->profile);
if (count($explodedprofile) === 2) {
// It'll either be an email or WebFinger entry.
$this->username = $explodedprofile[0];
$this->domain = $explodedprofile[1];
} else if (count($explodedprofile) === 3) {
// We may have a profile link as MoodleNet gives to the user.
$this->username = $explodedprofile[1];
$this->domain = $explodedprofile[2];
} else {
throw new \moodle_exception('invalidmoodlenetprofile', 'tool_moodlenet');
}
}
/**
* Get the full moodle net profile.
*
* @return string The moodle net profile.
*/
public function get_profile_name(): string {
return $this->profile;
}
/**
* Get the user ID that this profile belongs to.
*
* @return int The user ID.
*/
public function get_userid(): int {
return $this->userid;
}
/**
* Get the username for this profile.
*
* @return string The username.
*/
public function get_username(): string {
return $this->username;
}
/**
* Get the domain for this profile.
*
* @return string The domain.
*/
public function get_domain(): string {
return $this->domain;
}
}
@@ -0,0 +1,52 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Renderer.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\output;
defined('MOODLE_INTERNAL') || die();
use plugin_renderer_base;
/**
* Renderer class.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Defer to template.
*
* @param select_page $selectpage
* @return string HTML
*/
protected function render_select_page(select_page $selectpage): string {
$this->page->requires->js_call_amd('tool_moodlenet/select_page', 'init', [$selectpage->get_import_info()->get_id()]);
$data = $selectpage->export_for_template($this);
return parent::render_from_template('tool_moodlenet/select_page', $data);
}
}
@@ -0,0 +1,76 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Select page renderable.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\output;
defined('MOODLE_INTERNAL') || die;
use tool_moodlenet\local\import_info;
/**
* Select page renderable.
*
* @package tool_moodlenet
* @copyright 2020 Mathew May {@link https://mathew.solutions}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class select_page implements \renderable, \templatable {
/** @var import_info $importinfo resource and config information pertaining to an import. */
protected $importinfo;
/**
* Inits the Select page renderable.
*
* @param import_info $importinfo resource and config information pertaining to an import.
*/
public function __construct(import_info $importinfo) {
$this->importinfo = $importinfo;
}
/**
* Return the import info.
*
* @return import_info the import information.
*/
public function get_import_info(): import_info {
return $this->importinfo;
}
/**
* Export the data.
*
* @param \renderer_base $output
* @return \stdClass
*/
public function export_for_template(\renderer_base $output): \stdClass {
// Prepare the context object.
return (object) [
'name' => $this->importinfo->get_resource()->get_name(),
'type' => $this->importinfo->get_config()->type,
'cancellink' => new \moodle_url('/'),
];
}
}
@@ -0,0 +1,44 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy class for tool_moodlenet.
*
* @package tool_moodlenet
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_moodlenet\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy class for tool_moodlenet.
*
* @copyright 2020 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,349 @@
<?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(s($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(s($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);
$info = $curl->get_info();
// The base cURL seems fine, let's press on.
if (!$curl->get_errno() && !$curl->error) {
// 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'),
];
}
}
}
@@ -0,0 +1,35 @@
<?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/>.
declare(strict_types=1);
namespace tool_moodlenet\task;
/**
* Ad-hoc task to perform post install tasks.
* We use this to set the active activity chooser footer plugin to tool_moodlenet.
* We couldn't do this directly in install.php, because there is an admin_apply_default_settings() call after all plugins are
* installed and that would reset whatever value we had set earlier to 'hidden'.
*
* @package tool_moodlenet
* @copyright 2022 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class post_install extends \core\task\adhoc_task {
public function execute() {
set_config('activitychooseractivefooter', 'tool_moodlenet');
}
}
@@ -0,0 +1,47 @@
<?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/>.
declare(strict_types=1);
namespace tool_moodlenet\task;
/**
* Ad-hoc task to send the notification to admin stating MoodleNet is automatically enabled after upgrade.
*
* @package tool_moodlenet
* @copyright 2022 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send_enable_notification extends \core\task\adhoc_task {
public function execute(): void {
$message = new \core\message\message();
$message->component = 'moodle';
$message->name = 'notices';
$message->userfrom = \core_user::get_noreply_user();
$message->userto = get_admin();
$message->notification = 1;
$message->contexturl = (new \moodle_url('/admin/settings.php',
['section' => 'optionalsubsystems'], 'admin-enablemoodlenet'))->out(false);
$message->contexturlname = get_string('advancedfeatures', 'admin');
$message->subject = get_string('autoenablenotification_subject', 'tool_moodlenet');
$message->fullmessageformat = FORMAT_HTML;
$message->fullmessagehtml = get_string('autoenablenotification', 'tool_moodlenet', (object) [
'settingslink' => (new \moodle_url('/admin/settings.php', ['section' => 'tool_moodlenet']))->out(false),
]);
$message->smallmessage = strip_tags($message->fullmessagehtml);
message_send($message);
}
}
@@ -0,0 +1,45 @@
<?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/>.
declare(strict_types=1);
namespace tool_moodlenet\task;
use core\message\message;
/**
* Ad-hoc task to send a notification to admin stating that the user data related to the linked MoodleNet profiles has
* been removed.
*
* @package tool_moodlenet
* @copyright 2022 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send_mnet_profiles_data_removed_notification extends \core\task\adhoc_task {
public function execute(): void {
$message = new message();
$message->component = 'moodle';
$message->name = 'notices';
$message->userfrom = \core_user::get_noreply_user();
$message->userto = get_admin();
$message->notification = 1;
$message->subject = get_string('removedmnetprofilenotification_subject', 'tool_moodlenet');
$message->fullmessageformat = FORMAT_HTML;
$message->fullmessagehtml = get_string('removedmnetprofilenotification', 'tool_moodlenet');
$message->smallmessage = strip_tags($message->fullmessagehtml);
message_send($message);
}
}