MDL-87708 core: Remove MoodleNet outbound sharing

Part of MDL-87350
This commit is contained in:
Muhammad Arnaldo
2026-03-11 09:09:51 +07:00
committed by Huong Nguyen
parent 146fda5681
commit f9979de2a9
38 changed files with 47 additions and 3023 deletions
@@ -0,0 +1,10 @@
issueNumber: MDL-87350
notes:
core:
- message: >-
MoodleNet outbound sharing functionality has been removed. All classes,
functions, web services, and entry files related to outbound sharing
have been removed. The OAuth2 service type 'moodlenet' has also been
removed. There is no replacement for this functionality as MoodleNet
outbound sharing was an experimental feature that is being discontinued.
type: removed
-2
View File
@@ -622,8 +622,6 @@ $string['enableplugin'] = 'Enable {$a}';
$string['enablerecordcache'] = 'Enable record cache';
$string['enablerssfeeds'] = 'Enable RSS feeds';
$string['enablesearchareas'] = 'Enable search areas';
$string['enablesharingtomoodlenet'] = 'Enable sharing to MoodleNet (outbound)';
$string['enablesharingtomoodlenet_desc'] = 'Enable users to share course content to a configured MoodleNet instance if they have the relevant capability.';
$string['enablestats'] = 'Enable statistics';
$string['enabletrusttext'] = 'Enable trusted content';
$string['enableuserfeedback'] = 'Enable feedback about this software';
-1
View File
@@ -76,7 +76,6 @@ $string['cachedef_langmenu'] = 'List of available languages';
$string['cachedef_license'] = 'List of licences';
$string['cachedef_message_time_last_message_between_users'] = 'Time created for most recent message in a conversation';
$string['cachedef_modelfirstanalyses'] = 'First analysis by model and analysable';
$string['cachedef_moodlenet_usercanshare'] = 'Users can share resources to MoodleNet';
$string['cachedef_locking'] = 'Locking';
$string['cachedef_message_processors_enabled'] = "Message processors enabled status";
$string['cachedef_contextwithinsights'] = 'Context with insights';
-4
View File
@@ -427,10 +427,6 @@ $string['moduledisable'] = 'This module ({$a}) has been disabled for this partic
$string['moduledoesnotexist'] = 'This module does not exist';
$string['moduleinstancedoesnotexist'] = 'The instance of this module does not exist';
$string['modulemissingcode'] = 'Module {$a} is missing the code needed to perform this function';
$string['moodlenet:invalidshareformat'] = 'Incorrect MoodleNet share format';
$string['moodlenet:invalidsharestatus'] = 'Incorrect MoodleNet share status';
$string['moodlenet:invalidsharetype'] = 'Invalid MoodleNet share type';
$string['moodlenet:usernotconfigured'] = 'You do not have permission to share content to MoodleNet, or your account is incorrectly configured.';
$string['movecatcontentstoroot'] = 'Moving the category content to root is not allowed. You must move the contents to an existing category!';
$string['movecatcontentstoselected'] = 'Some category content cannot be moved into the selected category.';
$string['movecategorynotpossible'] = 'You cannot move category \'{$a}\' into the selected category.';
-40
View File
@@ -1388,38 +1388,6 @@ $string['moodledocslink'] = 'Documentation for this page';
$string['moodleversion'] = 'Moodle version';
$string['moodlerelease'] = 'Moodle release';
$string['moodleservicesandsupport'] = 'Services and support';
$string['moodlenet:cannotconnecttoserver'] = 'Cannot connect to MoodleNet server';
$string['moodlenet:columnname'] = 'Name';
$string['moodlenet:columnsenddate'] = 'Send date';
$string['moodlenet:columnsendstatus'] = 'Send status';
$string['moodlenet:columntype'] = 'Type';
$string['moodlenet:configoauthservice'] = 'Select a MoodleNet OAuth 2 service to enable sharing to that MoodleNet site. If the service doesn\'t exist yet, you will need to <a href="{$a}">create</a> it.';
$string['moodlenet:deletedactivity'] = 'Deleted activity';
$string['moodlenet:deletedcourse'] = 'Deleted course';
$string['moodlenet:eventresourceexported'] = 'Resource exported';
$string['moodlenet:gotomoodlenet'] = 'Go to MoodleNet drafts';
$string['moodlenet:issuerisnotauthorized'] = 'MoodleNet issuer is not authorised';
$string['moodlenet:issuerisnotenabled'] = 'MoodleNet issuer is not enabled';
$string['moodlenet:issuerisnotset'] = 'MoodleNet issuer is not set at site administration';
$string['moodlenet:nosharedresources'] = 'There are no shared resources to display at this time.';
$string['moodlenet:outboundsettings'] = 'MoodleNet outbound settings';
$string['moodlenet:sharenoticeactivity'] = 'This activity is being shared with MoodleNet as a resource.';
$string['moodlenet:sharenoticecourse'] = 'This course is being shared with MoodleNet as a resource.';
$string['moodlenet:sharenoticepartial'] = 'The selected activities are being shared with MoodleNet as a resource.';
$string['moodlenet:sharenoticepartialactivitynumber'] = '{$a} activities will be included in the course.';
$string['moodlenet:sharefailtitle'] = 'Something went wrong';
$string['moodlenet:sharefailtext'] = 'There was an error sharing your content to MoodleNet.<br>Please try again later.';
$string['moodlenet:sharefailtextwithsitesupport'] = 'There was an error sharing your content to MoodleNet.<br>Please try again later or <a href="{$a}">contact site support</a>.';
$string['moodlenet:sharefilesizelimitexceeded'] = 'The size of the resource being shared ({$a->filesize} bytes) exceeds the limit of {$a->filesizelimit} bytes.';
$string['moodlenet:shareprogress'] = 'MoodleNet share progress';
$string['moodlenet:shareprogressinfo'] = 'The list shows courses and activities you have shared to MoodleNet.<br>Click the resource name to view in MoodleNet.';
$string['moodlenet:sharesuccesstitle'] = 'Saved to MoodleNet drafts';
$string['moodlenet:sharesuccesstext'] = "Almost done! Visit your drafts in MoodleNet to finish sharing your content.";
$string['moodlenet:sharetomoodlenet'] = 'Share to MoodleNet';
$string['moodlenet:sharingstatus'] = 'Sharing to MoodleNet';
$string['moodlenet:sharinglargefile'] = "Large files can take some time.";
$string['moodlenet:sharingto'] = 'Sharing to: ';
$string['moodlenet:packagingandsending'] = 'Packaging your file and sending to MoodleNet...';
$string['more'] = 'more';
$string['morehelp'] = 'More help';
$string['morehelpaboutmodule'] = 'More help about the {$a} activity';
@@ -1741,14 +1709,6 @@ $string['privacy:metadata:log:module'] = 'module';
$string['privacy:metadata:log:time'] = 'The time when the action took place';
$string['privacy:metadata:log:url'] = 'The URL related to the event';
$string['privacy:metadata:log:userid'] = 'The ID of the user who performed the action';
$string['privacy:metadata:moodlenet_share_progress'] = 'MoodleNet share progress details';
$string['privacy:metadata:moodlenet_share_progress:type'] = 'The type of share that was performed';
$string['privacy:metadata:moodlenet_share_progress:courseid'] = 'The associated course ID';
$string['privacy:metadata:moodlenet_share_progress:cmid'] = 'The associated course module ID';
$string['privacy:metadata:moodlenet_share_progress:userid'] = 'The user that performed the share';
$string['privacy:metadata:moodlenet_share_progress:timecreated'] = 'The time the share was performed';
$string['privacy:metadata:moodlenet_share_progress:resourceurl'] = 'The returned URL from MoodleNet after a successful share';
$string['privacy:metadata:moodlenet_share_progress:status'] = 'The resulting status of the share';
$string['privacy:metadata:oauth2_refresh_token'] = 'Refresh token used in OAuth 2.0 communication';
$string['privacy:metadata:oauth2_refresh_token:issuerid'] = 'The ID of the issuer to which the token corresponds';
$string['privacy:metadata:oauth2_refresh_token:scopehash'] = 'The ID of the user to whom the token corresponds';
-2
View File
@@ -324,8 +324,6 @@ $string['manager'] = 'Manager';
$string['managerdescription'] = 'Managers can access courses and modify them, but usually do not participate in them.';
$string['manageroles'] = 'Manage roles';
$string['maybeassignedin'] = 'Context types where this role may be assigned';
$string['moodlenet:shareactivity'] = 'Share activities to MoodleNet';
$string['moodlenet:sharecourse'] = 'Share course to MoodleNet';
$string['morethan'] = 'More than {$a}';
$string['multipleroles'] = 'Multiple roles';
$string['my:manageblocks'] = 'Manage Dashboard page blocks';
-5
View File
@@ -184,11 +184,6 @@
"allowedlevel2": true,
"allowedspread": true
},
"moodlenet": {
"component": "core",
"allowedlevel2": false,
"allowedspread": false
},
"navigation": {
"component": "core",
"allowedlevel2": true,
@@ -1,78 +0,0 @@
<?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/>.
namespace core\event;
/**
* MoodleNet send attempt event.
*
* @package core
* @copyright 2023 Michael Hawkins <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_resource_exported extends \core\event\base {
/**
* Set basic properties for the event.
*
* @return void
*/
protected function init(): void {
$this->data['crud'] = 'c';
// Used by teachers, but not for direct educational value to their students.
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Fetch the localised general event name.
*
* @return string
*/
public static function get_name(): string {
return get_string('moodlenet:eventresourceexported');
}
/**
* Fetch the non-localised event description.
* This description format is designed to work for both single activity and course sharing.
*
* @return string
*/
public function get_description() {
$outcome = $this->other['success'] ? 'successfully shared' : 'failed to share';
if (!empty($this->other['cmids'])) {
$cmids = implode("', '", $this->other['cmids']);
$description = "The user with id '{$this->userid}' {$outcome} activities to MoodleNet with the " .
"following course module ids, from context with id '{$this->data['contextid']}': '{$cmids}'.";
} else if (!empty($this->other['courseid'])) {
$courseid = implode("', '", $this->other['courseid']);
$description = "The user with id '{$this->userid}' {$outcome} course to MoodleNet with the " .
"following course id, from context with id '{$this->data['contextid']}': '{$courseid}'.";
}
return rtrim($description, ", '");
}
/**
* Returns relevant URL.
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url($this->other['resourceurl']);
}
}
-148
View File
@@ -1,148 +0,0 @@
<?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/>.
namespace core\external;
use context_course;
use core\moodlenet\moodlenet_client;
use core\moodlenet\utilities;
use core\oauth2\api;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use moodle_url;
/**
* The external API to check whether a user has authorized for a given MoodleNet OAuth 2 issuer.
*
* @package core
* @copyright 2023 Huong Nguyen <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_auth_check extends external_api {
/**
* Returns description of parameters.
*
* @return external_function_parameters
* @since Moodle 4.2
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'issuerid' => new external_value(PARAM_INT, 'OAuth 2 issuer ID', VALUE_REQUIRED),
'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED),
]);
}
/**
* External function to check if the user is already authorized with MoodleNet.
*
* @param int $issuerid Issuer Id.
* @param int $courseid The course ID that contains the activity which being shared
* @return array
* @since Moodle 4.2
*/
public static function execute(int $issuerid, int $courseid): array {
global $USER;
[
'issuerid' => $issuerid,
'courseid' => $courseid,
] = self::validate_parameters(self::execute_parameters(), [
'issuerid' => $issuerid,
'courseid' => $courseid,
]);
// Check capability.
$coursecontext = context_course::instance($courseid);
$usercanshareactivity = utilities::can_user_share($coursecontext, $USER->id, 'activity');
$usercansharecourse = utilities::can_user_share($coursecontext, $USER->id, 'course');
if (!$usercanshareactivity && !$usercansharecourse) {
return self::return_errors($courseid, 'errorpermission',
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
}
// Get the issuer.
$issuer = api::get_issuer($issuerid);
// Validate the issuer and check if it is enabled or not.
if (!utilities::is_valid_instance($issuer)) {
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('invalidparameter', 'debug'));
}
$returnurl = new moodle_url('/admin/moodlenet_oauth2_callback.php');
$returnurl->param('issuerid', $issuerid);
$returnurl->param('callback', 'yes');
$returnurl->param('sesskey', sesskey());
// Get the OAuth Client.
if (!$oauthclient = api::get_user_oauth_client($issuer, $returnurl, moodlenet_client::API_SCOPE_CREATE_RESOURCE, true)) {
return self::return_errors($issuerid, 'erroroauthclient', get_string('invalidparameter', 'debug'));
}
$status = false;
$warnings = [];
$loginurl = '';
if (!$oauthclient->is_logged_in()) {
$loginurl = $oauthclient->get_login_url()->out(false);
} else {
$status = true;
}
return [
'status' => $status,
'loginurl' => $loginurl,
'warnings' => $warnings,
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
* @since Moodle 4.2
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'loginurl' => new external_value(PARAM_RAW, 'Login url'),
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings(),
]);
}
/**
* Handle return error.
*
* @param int $itemid Item id
* @param string $warningcode Warning code
* @param string $message Message
* @return array
*/
protected static function return_errors(int $itemid, string $warningcode, string $message): array {
$warnings[] = [
'item' => $itemid,
'warningcode' => $warningcode,
'message' => $message,
];
return [
'status' => false,
'loginurl' => '',
'warnings' => $warnings,
];
}
}
@@ -1,152 +0,0 @@
<?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/>.
namespace core\external;
use context_course;
use core\moodlenet\utilities;
use core\oauth2\api;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
/**
* The external API to het the activity information for MoodleNet sharing.
*
* @package core
* @copyright 2023 Huong Nguyen <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_get_share_info_activity extends external_api {
/**
* Returns description of parameters.
*
* @return external_function_parameters
* @since Moodle 4.2
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'cmid' => new external_value(PARAM_INT, 'The cmid of the activity', VALUE_REQUIRED),
]);
}
/**
* External function to get the activity information.
*
* @param int $cmid The course module id.
* @return array
* @since Moodle 4.2
*/
public static function execute(int $cmid): array {
global $CFG, $USER;
[
'cmid' => $cmid
] = self::validate_parameters(self::execute_parameters(), [
'cmid' => $cmid
]);
// Get course module.
$coursemodule = get_coursemodule_from_id(false, $cmid);
if (!$coursemodule) {
return self::return_errors($cmid, 'errorgettingactivityinformation', get_string('invalidcoursemodule', 'error'));
}
// Get course.
$course = get_course($coursemodule->course);
// Check capability.
$coursecontext = context_course::instance($course->id);
$usercanshare = utilities::can_user_share($coursecontext, $USER->id);
if (!$usercanshare) {
return self::return_errors($cmid, 'errorpermission',
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
}
$warnings = [];
$supporturl = utilities::get_support_url();
$issuerid = get_config('moodlenet', 'oauthservice');
if (empty($issuerid)) {
return self::return_errors(0, 'errorissuernotset', get_string('moodlenet:issuerisnotset', 'moodle'));
}
// Get the issuer.
$issuer = api::get_issuer($issuerid);
// Validate the issuer and check if it is enabled or not.
if (!utilities::is_valid_instance($issuer)) {
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('moodlenet:issuerisnotenabled', 'moodle'));
}
return [
'status' => true,
'name' => $coursemodule->name,
'type' => get_string('modulename', $coursemodule->modname),
'server' => $issuer->get_display_name(),
'supportpageurl' => $supporturl,
'issuerid' => $issuerid,
'warnings' => $warnings
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
* @since Moodle 4.2
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'name' => new external_value(PARAM_TEXT, 'Activity name'),
'type' => new external_value(PARAM_TEXT, 'Activity type'),
'server' => new external_value(PARAM_TEXT, 'MoodleNet server'),
'supportpageurl' => new external_value(PARAM_URL, 'Support page URL'),
'issuerid' => new external_value(PARAM_INT, 'MoodleNet issuer id'),
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
]);
}
/**
* Handle return error.
*
* @param int $itemid Item id.
* @param string $warningcode Warning code.
* @param string $message Message.
* @param int $issuerid Issuer id.
* @return array
*/
protected static function return_errors(int $itemid, string $warningcode, string $message, int $issuerid = 0): array {
$warnings[] = [
'item' => $itemid,
'warningcode' => $warningcode,
'message' => $message
];
return [
'status' => false,
'name' => '',
'type' => '',
'server' => '',
'supportpageurl' => '',
'issuerid' => $issuerid,
'warnings' => $warnings
];
}
}
@@ -1,143 +0,0 @@
<?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/>.
namespace core\external;
use context_course;
use core\moodlenet\utilities;
use core\oauth2\api;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
/**
* The external API to get MoodleNet information about the course to be shared.
*
* @package core
* @copyright 2023 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.3
*/
class moodlenet_get_shared_course_info extends external_api {
/**
* Returns description of parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'courseid' => new external_value(PARAM_INT, 'The course id', VALUE_REQUIRED),
]);
}
/**
* External function to get the course information.
*
* @param int $courseid The course id
* @return array
*/
public static function execute(int $courseid): array {
global $USER;
[
'courseid' => $courseid
] = self::validate_parameters(self::execute_parameters(), [
'courseid' => $courseid
]);
// Get course.
$course = get_course($courseid);
// Check capability.
$coursecontext = context_course::instance($course->id);
if (!utilities::can_user_share($coursecontext, $USER->id, 'course')) {
return self::return_errors($courseid, 'errorpermission',
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
}
$warnings = [];
$supporturl = utilities::get_support_url();
$issuerid = get_config('moodlenet', 'oauthservice');
if (empty($issuerid)) {
return self::return_errors(0, 'errorissuernotset', get_string('moodlenet:issuerisnotset', 'moodle'));
}
// Get the issuer.
$issuer = api::get_issuer($issuerid);
// Validate the issuer and check if it is enabled or not.
if (!utilities::is_valid_instance($issuer)) {
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('moodlenet:issuerisnotenabled', 'moodle'));
}
return [
'status' => true,
'name' => $course->fullname,
'type' => get_string('course'),
'server' => $issuer->get_display_name(),
'supportpageurl' => $supporturl,
'issuerid' => $issuerid,
'warnings' => $warnings
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'name' => new external_value(PARAM_TEXT, 'Course short name'),
'type' => new external_value(PARAM_TEXT, 'Course type'),
'server' => new external_value(PARAM_TEXT, 'MoodleNet server'),
'supportpageurl' => new external_value(PARAM_URL, 'Support page URL'),
'issuerid' => new external_value(PARAM_INT, 'MoodleNet issuer id'),
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
]);
}
/**
* Handle return error.
*
* @param int $itemid Item id.
* @param string $warningcode Warning code.
* @param string $message Message.
* @param int $issuerid Issuer id.
* @return array
*/
protected static function return_errors(int $itemid, string $warningcode, string $message, int $issuerid = 0): array {
$warnings[] = [
'item' => $itemid,
'warningcode' => $warningcode,
'message' => $message
];
return [
'status' => false,
'name' => '',
'type' => '',
'server' => '',
'supportpageurl' => '',
'issuerid' => $issuerid,
'warnings' => $warnings
];
}
}
-185
View File
@@ -1,185 +0,0 @@
<?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/>.
namespace core\external;
use context_course;
use core\http_client;
use core\moodlenet\activity_sender;
use core\moodlenet\moodlenet_client;
use core\moodlenet\utilities;
use core\moodlenet\share_recorder;
use core\oauth2\api;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use moodle_url;
/**
* The external API to send activity to MoodleNet.
*
* @package core
* @copyright 2023 Huong Nguyen <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_send_activity extends external_api {
/**
* Describes the parameters for sending the activity.
*
* @return external_function_parameters
* @since Moodle 4.2
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'issuerid' => new external_value(PARAM_INT, 'OAuth 2 issuer ID', VALUE_REQUIRED),
'cmid' => new external_value(PARAM_INT, 'Course module ID', VALUE_REQUIRED),
'shareformat' => new external_value(PARAM_INT, 'Share format', VALUE_REQUIRED),
]);
}
/**
* External function to send the activity to MoodleNet.
*
* @param int $issuerid The MoodleNet OAuth 2 issuer ID
* @param int $cmid The course module ID of the activity that being shared
* @param int $shareformat The share format being used, as defined by \core\moodlenet\activity_sender
* @return array
* @since Moodle 4.2
*/
public static function execute(int $issuerid, int $cmid, int $shareformat): array {
global $CFG, $USER;
[
'issuerid' => $issuerid,
'cmid' => $cmid,
'shareformat' => $shareformat,
] = self::validate_parameters(self::execute_parameters(), [
'issuerid' => $issuerid,
'cmid' => $cmid,
'shareformat' => $shareformat,
]);
// Check capability.
[$course] = get_course_and_cm_from_cmid($cmid);
$coursecontext = context_course::instance($course->id);
$usercanshare = utilities::can_user_share($coursecontext, $USER->id);
if (!$usercanshare) {
return self::return_errors($cmid, 'errorpermission',
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
}
// Check format.
if (!in_array($shareformat, [activity_sender::SHARE_FORMAT_BACKUP])) {
return self::return_errors($shareformat, 'errorinvalidformat', get_string('invalidparameter', 'debug'));
}
// Get the issuer.
$issuer = api::get_issuer($issuerid);
// Validate the issuer and check if it is enabled or not.
if (!utilities::is_valid_instance($issuer)) {
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('invalidparameter', 'debug'));
}
// Get the OAuth Client.
if (!$oauthclient = api::get_user_oauth_client(
$issuer,
new moodle_url($CFG->wwwroot),
moodlenet_client::API_SCOPE_CREATE_RESOURCE
)) {
return self::return_errors($issuerid, 'erroroauthclient', get_string('invalidparameter', 'debug'));
}
// Check login state.
if (!$oauthclient->is_logged_in()) {
return self::return_errors($issuerid, 'erroroauthclient', get_string('moodlenet:issuerisnotauthorized', 'moodle'));
}
// Get the HTTP Client.
$client = new http_client();
// Share activity.
try {
// Record activity share progress.
$shareid = share_recorder::insert_share_progress(share_recorder::TYPE_ACTIVITY, $USER->id, $course->id, $cmid);
$moodlenetclient = new moodlenet_client($client, $oauthclient);
$activitysender = new activity_sender($cmid, $USER->id, $moodlenetclient, $oauthclient, $shareformat);
$result = $activitysender->share_resource();
if (empty($result['drafturl'])) {
share_recorder::update_share_progress($shareid, share_recorder::STATUS_ERROR);
return self::return_errors($result['responsecode'], 'errorsendingactivity',
get_string('moodlenet:cannotconnecttoserver', 'moodle'));
}
} catch (\moodle_exception $e) {
share_recorder::update_share_progress($shareid, share_recorder::STATUS_ERROR);
return self::return_errors(0, 'errorsendingactivity', $e->getMessage());
}
share_recorder::update_share_progress($shareid, share_recorder::STATUS_SENT, $result['drafturl']);
return [
'status' => true,
'resourceurl' => $result['drafturl'],
'warnings' => [],
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
* @since Moodle 4.2
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'Status: true if success'),
// We used PARAM_TEXT instead of PARAM_URL because the URL return from MoodleNet may contain some characters.
// It does not match with PARAM_URL, but the URL still works.
// Since we just show the response resource URL to the user for them to navigate to MoodleNet, it would be safe.
'resourceurl' => new external_value(PARAM_TEXT, 'Resource URL from MoodleNet'),
'warnings' => new external_warnings(),
]);
}
/**
* Handle return error.
*
* @param int $itemid Item id
* @param string $warningcode Warning code
* @param string $message Message
* @return array
*/
protected static function return_errors(int $itemid, string $warningcode, string $message): array {
$warnings[] = [
'item' => $itemid,
'warningcode' => $warningcode,
'message' => $message,
];
return [
'status' => false,
'resourceurl' => '',
'warnings' => $warnings,
];
}
}
-244
View File
@@ -1,244 +0,0 @@
<?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/>.
namespace core\external;
use context_course;
use core\http_client;
use core\moodlenet\course_partial_sender;
use core\moodlenet\course_sender;
use core\moodlenet\moodlenet_client;
use core\moodlenet\share_recorder;
use core\moodlenet\utilities;
use core\oauth2\api;
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;
use core_external\external_warnings;
use moodle_url;
/**
* The external API to send course to MoodleNet.
*
* @package core
* @copyright 2023 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_send_course extends external_api {
/**
* Describes the parameters for sending the course.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'issuerid' => new external_value(PARAM_INT, 'OAuth 2 issuer ID', VALUE_REQUIRED),
'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED),
'shareformat' => new external_value(PARAM_INT, 'Share format', VALUE_REQUIRED),
'cmids' => new external_multiple_structure(
new external_value(PARAM_INT, 'Course module id', VALUE_DEFAULT, null, NULL_ALLOWED),
'List for course module ids', VALUE_DEFAULT, []
),
]);
}
/**
* External function to send the course to MoodleNet.
*
* @param int $issuerid The MoodleNet OAuth 2 issuer ID
* @param int $courseid The course ID
* @param int $shareformat The share format being used, as defined by \core\moodlenet\course_sender
* @param array $cmids The course module ids
* @return array
*/
public static function execute(
int $issuerid,
int $courseid,
int $shareformat,
array $cmids = [],
): array {
global $CFG, $USER;
[
'issuerid' => $issuerid,
'courseid' => $courseid,
'shareformat' => $shareformat,
'cmids' => $cmids,
] = self::validate_parameters(self::execute_parameters(), [
'issuerid' => $issuerid,
'courseid' => $courseid,
'shareformat' => $shareformat,
'cmids' => $cmids,
]);
// Partial sharing check.
$ispartialsharing = count($cmids) > 0;
// Check capability.
$coursecontext = context_course::instance($courseid);
$usercanshare = utilities::can_user_share($coursecontext, $USER->id, 'course');
if (!$usercanshare) {
return self::return_errors(
$courseid,
'errorpermission',
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle'))
);
}
// Check format.
if ($shareformat !== course_sender::SHARE_FORMAT_BACKUP) {
return self::return_errors(
$shareformat,
'errorinvalidformat',
get_string('invalidparameter', 'debug')
);
}
if ($ispartialsharing) {
// Check course modules in the course.
$modinfo = get_fast_modinfo($courseid);
$cms = $modinfo->get_cms();
foreach ($cmids as $cmid) {
if (!array_key_exists($cmid, $cms)) {
return self::return_errors(
$cmid,
'errorinvalidcmids',
get_string('invalidparameter', 'debug')
);
}
}
}
// Get the issuer.
$issuer = api::get_issuer($issuerid);
// Validate the issuer and check if it is enabled or not.
if (!utilities::is_valid_instance($issuer)) {
return self::return_errors(
$issuerid,
'errorissuernotenabled',
get_string('invalidparameter', 'debug')
);
}
// Get the OAuth Client.
if (!$oauthclient = api::get_user_oauth_client(
$issuer,
new moodle_url($CFG->wwwroot),
moodlenet_client::API_SCOPE_CREATE_RESOURCE
)) {
return self::return_errors(
$issuerid,
'erroroauthclient',
get_string('invalidparameter', 'debug')
);
}
// Check login state.
if (!$oauthclient->is_logged_in()) {
return self::return_errors(
$issuerid,
'erroroauthclient',
get_string('moodlenet:issuerisnotauthorized', 'moodle')
);
}
// Get the HTTP Client.
$client = new http_client();
// Share course.
try {
// Record course share progress.
$shareid = share_recorder::insert_share_progress(share_recorder::TYPE_COURSE, $USER->id, $courseid);
$moodlenetclient = new moodlenet_client($client, $oauthclient);
if ($ispartialsharing) {
$coursesender = new course_partial_sender($courseid, $USER->id, $moodlenetclient,
$oauthclient, $cmids, $shareformat);
} else {
$coursesender = new course_sender($courseid, $USER->id, $moodlenetclient, $oauthclient, $shareformat);
}
$result = $coursesender->share_resource();
if (empty($result['drafturl'])) {
share_recorder::update_share_progress($shareid, share_recorder::STATUS_ERROR);
return self::return_errors(
$result['responsecode'],
'errorsendingcourse',
get_string('moodlenet:cannotconnecttoserver', 'moodle')
);
}
} catch (\moodle_exception | \JsonException $e) {
share_recorder::update_share_progress($shareid, share_recorder::STATUS_ERROR);
return self::return_errors(
0,
'errorsendingcourse',
$e->getMessage()
);
}
share_recorder::update_share_progress($shareid, share_recorder::STATUS_SENT, $result['drafturl']);
return [
'status' => true,
'resourceurl' => $result['drafturl'],
'warnings' => [],
];
}
/**
* Describes the data returned from the external function.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'Status: true if success'),
// We used PARAM_TEXT instead of PARAM_URL because the URL return from MoodleNet may contain some characters.
// It does not match with PARAM_URL, but the URL still works.
// Since we just show the response resource URL to the user for them to navigate to MoodleNet, it would be safe.
'resourceurl' => new external_value(PARAM_TEXT, 'Resource URL from MoodleNet'),
'warnings' => new external_warnings(),
]);
}
/**
* Handle return error.
*
* @param int $itemid Item id
* @param string $warningcode Warning code
* @param string $message Message
* @return array
*/
protected static function return_errors(int $itemid, string $warningcode, string $message): array {
$warnings[] = [
'item' => $itemid,
'warningcode' => $warningcode,
'message' => $message,
];
return [
'status' => false,
'resourceurl' => '',
'warnings' => $warnings,
];
}
}
@@ -1,69 +0,0 @@
<?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/>.
namespace core\moodlenet;
use backup;
use backup_controller;
use cm_info;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
/**
* Packager to prepare appropriate backup of an activity to share to MoodleNet.
*
* @package core
* @copyright 2023 Raquel Ortega <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity_packager extends resource_packager {
/**
* Constructor.
*
* @param cm_info $cminfo context module information about the resource being packaged.
* @param int $userid The ID of the user performing the packaging.
*/
public function __construct(
cm_info $cminfo,
int $userid,
) {
// Check backup/restore support.
if (!plugin_supports('mod', $cminfo->modname , FEATURE_BACKUP_MOODLE2)) {
throw new \coding_exception("Cannot backup module $cminfo->modname. This module doesn't support the backup feature.");
}
parent::__construct($cminfo, $userid, $cminfo->modname);
}
/**
* Get the backup controller for the activity.
*
* @return backup_controller the backup controller for the activity.
*/
protected function get_backup_controller(): backup_controller {
return new backup_controller(
backup::TYPE_1ACTIVITY,
$this->cminfo->id,
backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO,
backup::MODE_GENERAL,
$this->userid
);
}
}
@@ -1,176 +0,0 @@
<?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/>.
namespace core\moodlenet;
use cm_info;
use core\event\moodlenet_resource_exported;
use core\oauth2\client;
use moodle_exception;
use stored_file;
/**
* API for sharing Moodle LMS activities to MoodleNet instances.
*
* @package core
* @copyright 2023 Michael Hawkins <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity_sender extends resource_sender {
/**
* @var cm_info The context module info object for the activity being shared.
*/
protected cm_info $cminfo;
/**
* Class constructor.
*
* @param int $cmid The course module ID of the activity being shared
* @param int $userid The user ID who is sharing the activity
* @param moodlenet_client $moodlenetclient The moodlenet_client object used to perform the share
* @param client $oauthclient The OAuth 2 client for the MoodleNet instance
* @param int $shareformat The data format to share in. Defaults to a Moodle backup (SHARE_FORMAT_BACKUP)
*/
public function __construct(
int $cmid,
protected int $userid,
protected moodlenet_client $moodlenetclient,
protected client $oauthclient,
protected int $shareformat = self::SHARE_FORMAT_BACKUP,
) {
parent::__construct($cmid, $userid, $moodlenetclient, $oauthclient, $shareformat);
[$this->course, $this->cminfo] = get_course_and_cm_from_cmid($cmid);
$this->packager = new activity_packager($this->cminfo, $this->userid);
}
/**
* Share an activity/resource to MoodleNet.
*
* @return array The HTTP response code from MoodleNet and the MoodleNet draft resource URL (URL empty string on fail).
* Format: ['responsecode' => 201, 'drafturl' => 'https://draft.mnurl/here']
*/
public function share_resource(): array {
$accesstoken = '';
$resourceurl = '';
$issuer = $this->oauthclient->get_issuer();
// Check user can share to the requested MoodleNet instance.
$coursecontext = \core\context\course::instance($this->course->id);
$usercanshare = utilities::can_user_share($coursecontext, $this->userid);
if ($usercanshare && utilities::is_valid_instance($issuer) && $this->oauthclient->is_logged_in()) {
$accesstoken = $this->oauthclient->get_accesstoken()->token;
}
// Throw an exception if the user is not currently set up to be able to share to MoodleNet.
if (!$accesstoken) {
throw new moodle_exception('moodlenet:usernotconfigured');
}
// Attempt to prepare and send the resource if validation has passed and we have an OAuth 2 token.
// Prepare file in requested format.
$filedata = $this->prepare_share_contents();
// If we have successfully prepared a file to share of permitted size, share it to MoodleNet.
if (!empty($filedata)) {
// Avoid sending a file larger than the defined limit.
$filesize = $filedata->get_filesize();
if ($filesize > self::MAX_FILESIZE) {
$filedata->delete();
throw new moodle_exception('moodlenet:sharefilesizelimitexceeded', 'core', '', [
'filesize' => $filesize,
'filesizelimit' => self::MAX_FILESIZE,
]);
}
// MoodleNet only accept plaintext descriptions.
$resourcedescription = $this->get_resource_description($coursecontext);
$response = $this->moodlenetclient->create_resource_from_stored_file(
$filedata,
$this->cminfo->name,
$resourcedescription,
);
$responsecode = $response->getStatusCode();
$responsebody = json_decode($response->getBody());
$resourceurl = $responsebody->homepage ?? '';
// Delete the generated file now it is no longer required.
// (It has either been sent, or failed - retries not currently supported).
$filedata->delete();
}
// Log every attempt to share (and whether or not it was successful).
$this->log_event($coursecontext, $this->cminfo->id, $resourceurl, $responsecode);
return [
'responsecode' => $responsecode,
'drafturl' => $resourceurl,
];
}
/**
* Log an event to the admin logs for an outbound share attempt.
*
* @param \context $coursecontext The course context being shared from.
* @param int $cmid The CMID of the activity being shared.
* @param string $resourceurl The URL of the draft resource if it was created.
* @param int $responsecode The HTTP response code describing the outcome of the attempt.
* @return void
*/
protected function log_event(
\core\context $coursecontext,
int $cmid,
string $resourceurl,
int $responsecode,
): void {
$event = moodlenet_resource_exported::create([
'context' => $coursecontext,
'other' => [
'cmids' => [$cmid],
'resourceurl' => $resourceurl,
'success' => ($responsecode == 201),
],
]);
$event->trigger();
}
/**
* Fetch the description for the resource being created, in a supported text format.
*
* @param \context $coursecontext The course context being shared from.
* @return string Converted activity description.
*/
protected function get_resource_description(
\context $coursecontext,
): string {
global $PAGE, $DB;
// We need to set the page context here because content_to_text and format_text will need the page context to work.
$PAGE->set_context($coursecontext);
$intro = $DB->get_record($this->cminfo->modname, ['id' => $this->cminfo->instance], 'intro, introformat', MUST_EXIST);
$processeddescription = strip_tags($intro->intro);
$processeddescription = content_to_text(format_text(
$processeddescription,
$intro->introformat,
), $intro->introformat);
return $processeddescription;
}
}
@@ -1,64 +0,0 @@
<?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/>.
namespace core\moodlenet;
use backup;
use backup_controller;
use stdClass;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
/**
* Packager to prepare appropriate backup of a course to share to MoodleNet.
*
* @package core
* @copyright 2023 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_packager extends resource_packager {
/**
* Constructor for course packager.
*
* @param stdClass $course The course to package
* @param int $userid The ID of the user performing the packaging
*/
public function __construct(
stdClass $course,
int $userid,
) {
parent::__construct($course, $userid, $course->shortname);
}
/**
* Get the backup controller for the course.
*
* @return backup_controller the backup controller for the course.
*/
protected function get_backup_controller(): backup_controller {
return new backup_controller(
backup::TYPE_1COURSE,
$this->course->id,
backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO,
backup::MODE_GENERAL,
$this->userid,
);
}
}
@@ -1,99 +0,0 @@
<?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/>.
namespace core\moodlenet;
use backup_activity_task;
use backup_controller;
use stdClass;
use stored_file;
/**
* Packager to prepare appropriate backup of a number of activities in a course to share to MoodleNet.
*
* @package core
* @copyright 2023 Huong Nguyen <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_partial_packager extends course_packager {
/**
* @var int[] $cmids List of course module ids of selected activities.
*/
protected array $cmids;
/**
* Constructor for course partial packager.
*
* @param stdClass $course The course to package
* @param array $cmids List of course module id of selected activities.
* @param int $userid The ID of the user performing the packaging
*/
public function __construct(
stdClass $course,
array $cmids,
int $userid,
) {
$this->cmids = $cmids;
parent::__construct($course, $userid);
}
/**
* Package the resource identified by resource id into a new stored_file.
*
* @param backup_controller $controller The backup controller.
* @return stored_file
*/
protected function package(backup_controller $controller): stored_file {
$this->remove_unselected_activities($controller);
return parent::package($controller);
}
/**
* Remove unselected activities in the course backup.
*
* @param backup_controller $controller The backup controller.
*/
protected function remove_unselected_activities(backup_controller $controller): void {
foreach ($this->get_all_activity_tasks($controller) as $task) {
foreach ($task->get_settings() as $setting) {
if (in_array($task->get_moduleid(), $this->cmids) &&
str_contains($setting->get_name(), '_included') !== false) {
$setting->set_value(1);
} else {
$setting->set_value(0);
}
}
}
}
/**
* Get all the activity tasks in the controller.
*
* @param backup_controller $controller The backup controller.
* @return backup_activity_task[] Array of activity tasks.
*/
protected function get_all_activity_tasks(backup_controller $controller): array {
$tasks = [];
foreach ($controller->get_plan()->get_tasks() as $task) {
if (! $task instanceof backup_activity_task) { // Only for activity tasks.
continue;
}
$tasks[] = $task;
}
return $tasks;
}
}
@@ -1,100 +0,0 @@
<?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/>.
namespace core\moodlenet;
use core\event\moodlenet_resource_exported;
use core\oauth2\client;
use moodle_exception;
/**
* API for sharing a number of Moodle LMS activities as a course backup to MoodleNet instances.
*
* @package core
* @copyright 2023 Huong Nguyen <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_partial_sender extends course_sender {
/**
* Constructor for course sender.
*
* @param int $courseid The course ID of the course being shared
* @param int $userid The user ID who is sharing the activity
* @param moodlenet_client $moodlenetclient The moodlenet_client object used to perform the share
* @param client $oauthclient The OAuth 2 client for the MoodleNet instance
* @param int $shareformat The data format to share in. Defaults to a Moodle backup (SHARE_FORMAT_BACKUP)
*/
public function __construct(
int $courseid,
protected int $userid,
protected moodlenet_client $moodlenetclient,
protected client $oauthclient,
protected array $cmids,
protected int $shareformat = self::SHARE_FORMAT_BACKUP,
) {
parent::__construct($courseid, $userid, $moodlenetclient, $oauthclient, $shareformat);
$this->validate_course_module_ids($this->course, $this->cmids);
$this->packager = new course_partial_packager($this->course, $this->cmids, $this->userid);
}
/**
* Log an event to the admin logs for an outbound share attempt.
*
* @param string $resourceurl The URL of the draft resource if it was created
* @param int $responsecode The HTTP response code describing the outcome of the attempt
* @return void
*/
protected function log_event(
string $resourceurl,
int $responsecode,
): void {
$event = moodlenet_resource_exported::create([
'context' => $this->coursecontext,
'other' => [
'cmids' => $this->cmids,
'courseid' => [$this->course->id],
'resourceurl' => $resourceurl,
'success' => ($responsecode === 201),
],
]);
$event->trigger();
}
/**
* Validate the course module ids.
*
* @param \stdClass $course Course object
* @param array $cmids List of course module ids to check
* @return void
*/
protected function validate_course_module_ids(
\stdClass $course,
array $cmids,
): void {
if (empty($cmids)) {
throw new moodle_exception('invalidcoursemodule');
}
$modinfo = get_fast_modinfo($course);
$cms = $modinfo->get_cms();
foreach ($cmids as $cmid) {
if (!array_key_exists($cmid, $cms)) {
throw new moodle_exception('invalidcoursemodule');
}
}
}
}
@@ -1,180 +0,0 @@
<?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/>.
namespace core\moodlenet;
use core\event\moodlenet_resource_exported;
use core\oauth2\client;
use moodle_exception;
use stored_file;
/**
* API for sharing Moodle LMS courses to MoodleNet instances.
*
* @package core
* @copyright 2023 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_sender extends resource_sender {
/**
* @var \core\context\course|false The course context.
*/
protected \core\context\course|false $coursecontext;
/**
* Constructor for course sender.
*
* @param int $courseid The course ID of the course being shared
* @param int $userid The user ID who is sharing the activity
* @param moodlenet_client $moodlenetclient The moodlenet_client object used to perform the share
* @param client $oauthclient The OAuth 2 client for the MoodleNet instance
* @param int $shareformat The data format to share in. Defaults to a Moodle backup (SHARE_FORMAT_BACKUP)
*/
public function __construct(
int $courseid,
protected int $userid,
protected moodlenet_client $moodlenetclient,
protected client $oauthclient,
protected int $shareformat = self::SHARE_FORMAT_BACKUP,
) {
parent::__construct($courseid, $userid, $moodlenetclient, $oauthclient, $shareformat);
$this->course = get_course($courseid);
$this->coursecontext = \core\context\course::instance($courseid);
$this->packager = new course_packager($this->course, $this->userid);
}
/**
* Share a course to MoodleNet.
*
* @return array The HTTP response code from MoodleNet and the MoodleNet draft resource URL (URL empty string on fail).
* Format: ['responsecode' => 201, 'drafturl' => 'https://draft.mnurl/here']
*/
public function share_resource(): array {
$accesstoken = '';
$issuer = $this->oauthclient->get_issuer();
// Check user can share to the requested MoodleNet instance.
$usercanshare = utilities::can_user_share($this->coursecontext, $this->userid, 'course');
if ($usercanshare && utilities::is_valid_instance($issuer) && $this->oauthclient->is_logged_in()) {
$accesstoken = $this->oauthclient->get_accesstoken()->token;
}
// Throw an exception if the user is not currently set up to be able to share to MoodleNet.
if (!$accesstoken) {
throw new moodle_exception('moodlenet:usernotconfigured');
}
// Attempt to prepare and send the resource if validation has passed and we have an OAuth 2 token.
// Prepare file in requested format.
$filedata = $this->prepare_share_contents();
// Avoid sending a file larger than the defined limit.
$filesize = $filedata->get_filesize();
if ($filesize > self::MAX_FILESIZE) {
$filedata->delete();
throw new moodle_exception('moodlenet:sharefilesizelimitexceeded', 'core', '', [
'filesize' => $filesize,
'filesizelimit' => self::MAX_FILESIZE,
]);
}
// MoodleNet only accept plaintext descriptions.
$resourcedescription = $this->get_resource_description();
$response = $this->moodlenetclient->create_resource_from_stored_file(
$filedata,
$this->course->fullname,
$resourcedescription,
);
$responsecode = $response->getStatusCode();
$responsebody = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
$resourceurl = $responsebody->homepage ?? '';
// Delete the generated file now it is no longer required.
// (It has either been sent, or failed - retries not currently supported).
$filedata->delete();
// Log every attempt to share (and whether it was successful).
$this->log_event($resourceurl, $responsecode);
return [
'responsecode' => $responsecode,
'drafturl' => $resourceurl,
];
}
/**
* Log an event to the admin logs for an outbound share attempt.
*
* @param string $resourceurl The URL of the draft resource if it was created
* @param int $responsecode The HTTP response code describing the outcome of the attempt
* @return void
*/
protected function log_event(
string $resourceurl,
int $responsecode,
): void {
$event = moodlenet_resource_exported::create([
'context' => $this->coursecontext,
'other' => [
'courseid' => [$this->course->id],
'resourceurl' => $resourceurl,
'success' => ($responsecode === 201),
],
]);
$event->trigger();
}
/**
* Return the list of supported share formats.
*
* @return array Array of supported share format values.
*/
protected static function get_allowed_share_formats(): array {
return [
self::SHARE_FORMAT_BACKUP,
];
}
/**
* Fetch the description for the resource being created, in a supported text format.
*
* @return string Converted course description.
*/
protected function get_resource_description(): string {
global $PAGE;
// We need to set the page context here because content_to_text and format_text will need the page context to work.
$PAGE->set_context($this->coursecontext);
$processeddescription = strip_tags($this->course->summary);
$processeddescription = content_to_text
(
format_text(
$processeddescription,
$this->course->summaryformat,
),
$this->course->summaryformat
);
return $processeddescription;
}
}
@@ -1,139 +0,0 @@
<?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/>.
namespace core\moodlenet;
use core\http_client;
use core\oauth2\client;
use stored_file;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
/**
* MoodleNet client which handles direct outbound communication with MoodleNet instances.
*
* @package core
* @copyright 2023 Michael Hawkins <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodlenet_client {
/**
* @var string MoodleNet resource creation endpoint URI.
*/
protected const API_CREATE_RESOURCE_URI = '/.pkg/@moodlenet/ed-resource/basic/v1/create';
/**
* @var string MoodleNet scope for creating resources.
*/
public const API_SCOPE_CREATE_RESOURCE = '@moodlenet/ed-resource:write.own';
/**
* Constructor.
*
* @param http_client $httpclient The httpclient object being used to perform the share.
* @param client $oauthclient The OAuth 2 client for the MoodleNet site being shared to.
*/
public function __construct(
protected http_client $httpclient,
protected client $oauthclient,
) {
// All properties promoted, nothing further required.
}
/**
* Create a resource on MoodleNet which includes a file.
*
* @param stored_file $file The file data to send to MoodleNet.
* @param string $resourcename The name of the resource being shared.
* @param string $resourcedescription A description of the resource being shared.
* @return \Psr\Http\Message\ResponseInterface The HTTP client response from MoodleNet.
*/
public function create_resource_from_stored_file(
stored_file $file,
string $resourcename,
string $resourcedescription,
): ResponseInterface {
// This may take a long time if a lot of data is being shared.
\core_php_time_limit::raise();
$moodleneturl = $this->oauthclient->get_issuer()->get('baseurl');
$apiurl = rtrim($moodleneturl, '/') . self::API_CREATE_RESOURCE_URI;
$stream = $file->get_psr_stream();
$requestdata = $this->prepare_file_share_request_data(
$file->get_filename(),
$file->get_mimetype(),
$stream,
$resourcename,
$resourcedescription,
);
try {
$response = $this->httpclient->request('POST', $apiurl, $requestdata);
} finally {
$stream->close(); // Always close the request stream ASAP. Or it will remain open till shutdown/destruct.
}
return $response;
}
/**
* Prepare the request data required for sharing a file to MoodleNet.
* This creates an array in the format used by \core\httpclient options to send a multipart request.
*
* @param string $filename Name of the file being shared.
* @param string $mimetype Mime type of the file being shared.
* @param StreamInterface $stream Stream of the file being shared.
* @param string $resourcename The name of the resource being shared.
* @param string $resourcedescription A description of the resource being shared.
* @return array Data in the format required to send a file to MoodleNet using \core\httpclient.
*/
protected function prepare_file_share_request_data(
string $filename,
string $mimetype,
StreamInterface $stream,
string $resourcename,
string $resourcedescription,
): array {
return [
'headers' => [
'Authorization' => 'Bearer ' . $this->oauthclient->get_accesstoken()->token,
],
'multipart' => [
[
'name' => 'metadata',
'contents' => json_encode([
'name' => $resourcename,
'description' => $resourcedescription,
]),
'headers' => [
'Content-Disposition' => 'form-data; name="."',
],
],
[
'name' => 'filecontents',
'contents' => $stream,
'headers' => [
'Content-Disposition' => 'form-data; name=".resource"; filename="' . $filename . '"',
'Content-Type' => $mimetype,
'Content-Transfer-Encoding' => 'binary',
],
],
],
];
}
}
@@ -1,191 +0,0 @@
<?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/>.
namespace core\moodlenet;
use backup_controller;
use backup_root_task;
use cm_info;
use core\context\user;
use stdClass;
use stored_file;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
/**
* Base packager to prepare appropriate backup of a resource to share to MoodleNet.
*
* @package core
* @copyright 2023 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class resource_packager {
/**
* @var string $resourcefilename The filename for the resource.
*/
protected string $resourcefilename = 'resource';
/**
* @var stdClass $course The course which the resource belongs to.
*/
protected stdClass $course;
/**
* @var cm_info $cminfo The course module which the resource belongs to.
*/
protected cm_info $cminfo;
/**
* @var int $userid The ID of the user performing the packaging.
*/
protected int $userid;
/**
* Constructor for the base packager.
*
* @param stdClass|cm_info $resource The resource object
* @param int $userid The user id
*/
public function __construct(
stdClass|cm_info $resource,
int $userid,
string $resourcefilename,
) {
if ($resource instanceof cm_info) {
$this->cminfo = $resource;
$this->course = $resource->get_course();
} else {
$this->course = $resource;
}
$this->userid = $userid;
$this->resourcefilename = $resourcefilename;
}
/**
* Get the backup controller for the course.
*
* @return backup_controller The backup controller instance that will be used to package the resource.
*/
abstract protected function get_backup_controller(): backup_controller;
/**
* Prepare the backup file using appropriate setting overrides and return relevant information.
*
* @return stored_file
*/
public function get_package(): stored_file {
$controller = $this->get_backup_controller();
$alltasksettings = $this->get_all_task_settings($controller);
// Override relevant settings to remove user data when packaging to share to MoodleNet.
$this->override_task_setting($alltasksettings, 'setting_root_users', 0);
$this->override_task_setting($alltasksettings, 'setting_root_role_assignments', 0);
$this->override_task_setting($alltasksettings, 'setting_root_blocks', 0);
$this->override_task_setting($alltasksettings, 'setting_root_comments', 0);
$this->override_task_setting($alltasksettings, 'setting_root_badges', 0);
$this->override_task_setting($alltasksettings, 'setting_root_userscompletion', 0);
$this->override_task_setting($alltasksettings, 'setting_root_logs', 0);
$this->override_task_setting($alltasksettings, 'setting_root_grade_histories', 0);
$this->override_task_setting($alltasksettings, 'setting_root_groups', 0);
$storedfile = $this->package($controller);
$controller->destroy(); // We are done with the controller, destroy it.
return $storedfile;
}
/**
* Get all backup settings available for override.
*
* @return array the associative array of taskclass => settings instances.
*/
protected function get_all_task_settings(backup_controller $controller): array {
$tasksettings = [];
foreach ($controller->get_plan()->get_tasks() as $task) {
$taskclass = get_class($task);
$tasksettings[$taskclass] = $task->get_settings();
}
return $tasksettings;
}
/**
* Override a backup task setting with a given value.
*
* @param array $alltasksettings All task settings.
* @param string $settingname The name of the setting to be overridden (task class name format).
* @param int $settingvalue Value to be given to the setting.
*/
protected function override_task_setting(array $alltasksettings, string $settingname, int $settingvalue): void {
if (empty($rootsettings = $alltasksettings[backup_root_task::class])) {
return;
}
foreach ($rootsettings as $setting) {
$name = $setting->get_ui_name();
if ($name == $settingname && $settingvalue != $setting->get_value()) {
$setting->set_value($settingvalue);
return;
}
}
}
/**
* Package the resource identified by resource id into a new stored_file.
*
* @param backup_controller $controller The backup controller.
* @return stored_file
*/
protected function package(backup_controller $controller): stored_file {
// Execute the backup and fetch the result.
$controller->execute_plan();
$result = $controller->get_results();
if (!isset($result['backup_destination'])) {
throw new \moodle_exception('Failed to package resource.');
}
$backupfile = $result['backup_destination'];
if (!$backupfile->get_contenthash()) {
throw new \moodle_exception('Failed to package resource (invalid file).');
}
// Create the location we want to copy this file to.
$filerecord = [
'contextid' => user::instance($this->userid)->id,
'userid' => $this->userid,
'component' => 'user',
'filearea' => 'draft',
'itemid' => file_get_unused_draft_itemid(),
'filepath' => '/',
'filename' => $this->resourcefilename . '_backup.mbz',
];
// Create the local file based on the backup.
$fs = get_file_storage();
$file = $fs->create_file_from_storedfile($filerecord, $backupfile);
// Delete the backup now it has been created in the file area.
$backupfile->delete();
return $file;
}
}
@@ -1,103 +0,0 @@
<?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/>.
namespace core\moodlenet;
use core\oauth2\client;
use moodle_exception;
use stdClass;
use stored_file;
/**
* API for sharing Moodle LMS resources to MoodleNet instances.
*
* @package core
* @copyright 2023 Safat Shahin <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class resource_sender {
/**
* @var int Backup share format - the content is being shared as a Moodle backup file.
*/
public const SHARE_FORMAT_BACKUP = 0;
/**
* @var int Maximum upload file size (1.07 GB).
*/
public const MAX_FILESIZE = 1070000000;
/**
* @var stdClass The course where the activity is located.
*/
protected stdClass $course;
/** @var resource_packager Resource packager. */
protected resource_packager $packager;
/**
* Class constructor.
*
* @param int $resourceid The resource ID of the resource being shared.
* @param int $userid The user ID who is sharing the activity.
* @param moodlenet_client $moodlenetclient The moodlenet_client object used to perform the share.
* @param client $oauthclient The OAuth 2 client for the MoodleNet instance.
* @param int $shareformat The data format to share in. Defaults to a Moodle backup (SHARE_FORMAT_BACKUP).
* @throws moodle_exception
*/
public function __construct(
int $resourceid,
protected int $userid,
protected moodlenet_client $moodlenetclient,
protected client $oauthclient,
protected int $shareformat = self::SHARE_FORMAT_BACKUP,
) {
if (!in_array($shareformat, self::get_allowed_share_formats(), true)) {
throw new moodle_exception('moodlenet:invalidshareformat');
}
}
/**
* Return the list of supported share formats.
*
* @return array Array of supported share format values.
*/
protected static function get_allowed_share_formats(): array {
return [
self::SHARE_FORMAT_BACKUP,
];
}
/**
* Share a resource to MoodleNet.
*
* @return array The HTTP response code from MoodleNet and the MoodleNet draft resource URL (URL empty string on fail).
* Format: ['responsecode' => 201, 'drafturl' => 'https://draft.mnurl/here']
*/
abstract public function share_resource(): array;
/**
* Prepare the data for sharing, in the format specified.
*
* @return stored_file
*/
protected function prepare_share_contents(): stored_file {
return match ($this->shareformat) {
self::SHARE_FORMAT_BACKUP => $this->packager->get_package(),
default => throw new \coding_exception("Unknown share format: {$this->shareformat}'"),
};
}
}
@@ -1,252 +0,0 @@
<?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/>.
/**
* MoodleNet share progress table.
*
* @package core
* @copyright 2023 David Woloszyn <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\moodlenet;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/tablelib.php');
use html_writer;
use moodle_url;
use stdClass;
use table_sql;
/**
* MoodleNet share progress table.
*
* @package core
* @copyright 2023 David Woloszyn <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class share_progress_table extends table_sql {
/** @var int The user id records will be displayed for. */
protected $userid;
/**
* Set up the table.
*
* @param string $uniqueid Unique id of table.
* @param moodle_url $url The base URL.
* @param int $userid The user id.
*/
public function __construct($uniqueid, $url, $userid) {
parent::__construct($uniqueid);
$this->userid = $userid;
$this->define_table_columns();
$this->define_baseurl($url);
$this->define_table_configs();
}
/**
* Define table configs.
*/
protected function define_table_configs() {
$this->collapsible(false);
$this->sortable(false);
$this->pageable(true);
$this->set_default_per_page(25);
}
/**
* Set up the columns and headers.
*/
protected function define_table_columns() {
// Define headers and columns.
$cols = [
'name' => get_string('name'),
'type' => get_string('moodlenet:columntype'),
'timecreated' => get_string('moodlenet:columnsenddate'),
'status' => get_string('moodlenet:columnsendstatus'),
];
$this->define_columns(array_keys($cols));
$this->define_headers(array_values($cols));
$this->column_class('status', 'text-center');
}
/**
* Name column.
*
* @param stdClass $row Row data.
* @return string
*/
protected function col_name(stdClass $row): string {
global $OUTPUT;
$class = '';
// Track deletion of resources on Moodle.
$deleted = false;
// Courses.
if ($row->type == share_recorder::TYPE_COURSE) {
if ($row->fullname) {
$name = $row->fullname;
} else {
$name = get_string('moodlenet:deletedcourse');
$deleted = true;
}
// Activities.
} else if ($row->type == share_recorder::TYPE_ACTIVITY) {
if ($cm = get_coursemodule_from_id('', $row->cmid)) {
$name = $cm->name;
} else {
$name = get_string('moodlenet:deletedactivity');
$deleted = true;
}
}
if ($deleted) {
$class = 'fst-italic';
}
// Add a link to the resource if it was recorded.
if (!empty($row->resourceurl)) {
// Apply bold to resource links that aren't deleted.
if (!$deleted) {
$class = 'fw-bold';
}
$icon = $OUTPUT->pix_icon('i/externallink', get_string('opensinnewwindow'), 'moodle', ['class' => 'ms-1']);
$text = $name . $icon;
$attributes = [
'target' => '_blank',
'rel' => 'noopener noreferrer',
];
$name = html_writer::link($row->resourceurl, $text, $attributes);
}
return html_writer::span($name, $class);
}
/**
* Type column.
*
* @param stdClass $row Row data.
* @return string
*/
protected function col_type(stdClass $row): string {
// Courses.
if ($row->type == share_recorder::TYPE_COURSE) {
$type = get_string('course');
// Activities.
} else if ($row->type == share_recorder::TYPE_ACTIVITY) {
if ($row->modname) {
$type = get_string('modulename', $row->modname);
} else {
// Alternatively, default to 'activity'.
$type = get_string('activity');
}
}
return $type;
}
/**
* Time created column (Send date).
*
* @param stdClass $row Row data.
* @return string
*/
protected function col_timecreated(stdClass $row): string {
$format = get_string('strftimedatefullshort', 'core_langconfig');
return userdate($row->timecreated, $format);
}
/**
* Status column (Send status).
*
* @param stdClass $row Row data.
* @return string
*/
protected function col_status(stdClass $row): string {
// Display a badge indicating the status of the share.
if ($row->status == share_recorder::STATUS_IN_PROGRESS) {
$status = html_writer::span(get_string('inprogress'), 'badge bg-warning text-dark');
} else if ($row->status == share_recorder::STATUS_SENT) {
$status = html_writer::span(get_string('sent'), 'badge bg-success text-white');
} else if ($row->status == share_recorder::STATUS_ERROR) {
$status = html_writer::span(get_string('error'), 'badge bg-danger text-white');
}
return $status;
}
/**
* Builds the SQL query.
*
* @param bool $count When true, return the count SQL.
* @return array containing sql to use and an array of params.
*/
protected function get_sql_and_params($count = false) {
if ($count) {
$select = "COUNT(1)";
} else {
$select = "msp.id, msp.type, msp.courseid, msp.cmid, msp.timecreated, " .
"msp.resourceurl, msp.status, c.fullname, md.name AS modname";
}
$sql = "SELECT $select
FROM {moodlenet_share_progress} msp
LEFT JOIN {course} c ON c.id = msp.courseid
LEFT JOIN {course_modules} cm ON cm.course = msp.courseid
AND cm.id = msp.cmid
LEFT JOIN {modules} md ON md.id = cm.module
WHERE msp.userid = :userid";
$params = ['userid' => $this->userid];
if (!$count) {
$sql .= " ORDER BY msp.status DESC, msp.timecreated DESC";
}
return [$sql, $params];
}
/**
* Query the DB.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar do you want to use the initials bar.
*/
public function query_db($pagesize, $useinitialsbar = true) {
global $DB;
list($countsql, $countparams) = $this->get_sql_and_params(true);
list($sql, $params) = $this->get_sql_and_params();
$total = $DB->count_records_sql($countsql, $countparams);
$this->pagesize($pagesize, $total);
$this->rawdata = $DB->get_records_sql($sql, $params, $this->get_page_start(), $this->get_page_size());
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars($total > $pagesize);
}
}
/**
* Notification to display when there are no results.
*/
public function print_nothing_to_display() {
global $OUTPUT;
echo $OUTPUT->notification(get_string('moodlenet:nosharedresources'), 'info');
}
}
@@ -1,132 +0,0 @@
<?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/>.
namespace core\moodlenet;
use moodle_exception;
use stdClass;
/**
* Record the sharing of content to MoodleNet.
*
* @package core
* @copyright 2023 David Woloszyn <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class share_recorder {
/**
* @var int The content being shared is a course.
*/
public const TYPE_COURSE = 1;
/**
* @var int The content being shared is an activity.
*/
public const TYPE_ACTIVITY = 2;
/**
* @var int The status of the share is 'sent'.
*/
public const STATUS_SENT = 1;
/**
* @var int The status of the share is 'in progress'.
*/
public const STATUS_IN_PROGRESS = 2;
/**
* @var int The status of the share is 'error'.
*/
public const STATUS_ERROR = 3;
/**
* Get all allowed share types.
*
* @return array
*/
protected static function get_allowed_share_types(): array {
return [
self::TYPE_ACTIVITY,
self::TYPE_COURSE
];
}
/**
* Get all allowed share statuses.
* Note that the particular status values aid in sorting.
*
* @return array
*/
protected static function get_allowed_share_statuses(): array {
return [
self::STATUS_SENT,
self::STATUS_IN_PROGRESS,
self::STATUS_ERROR,
];
}
/**
* Create a new share progress record in the DB.
*
* @param int $sharetype The type of share (e.g. TYPE_COURSE).
* @param int $userid The ID of the user performing the share.
* @param int $courseid The associated course id.
* @param int|null $cmid The associated course module id (when sharing activity).
* @return int Returns the inserted record id.
*/
public static function insert_share_progress(int $sharetype, int $userid, int $courseid, ?int $cmid = null): int {
global $DB, $USER;
if (!in_array($sharetype, self::get_allowed_share_types())) {
throw new moodle_exception('moodlenet:invalidsharetype');
}
$data = new stdClass();
$data->type = $sharetype;
$data->courseid = $courseid;
$data->cmid = $cmid;
$data->userid = $userid;
$data->timecreated = time();
$data->status = self::STATUS_IN_PROGRESS;
return $DB->insert_record('moodlenet_share_progress', $data);
}
/**
* Update the share progress record in the DB.
*
* @param int $shareid The id of the share progress row being updated.
* @param int $status The status of the share progress (e.g. STATUS_SENT).
* @param string|null $resourceurl The resource url returned from MoodleNet.
*/
public static function update_share_progress(int $shareid, int $status, ?string $resourceurl = null): void {
global $DB;
if (!in_array($status, self::get_allowed_share_statuses())) {
throw new moodle_exception('moodlenet:invalidsharestatus');
}
$data = new stdClass();
$data->id = $shareid;
$data->resourceurl = $resourceurl;
$data->status = $status;
$DB->update_record('moodlenet_share_progress', $data);
}
}
-123
View File
@@ -1,123 +0,0 @@
<?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/>.
namespace core\moodlenet;
use core\oauth2\issuer;
/**
* Class containing static utilities (such as various checks) required by the MoodleNet API.
*
* @package core
* @copyright 2023 Michael Hawkins <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class utilities {
/**
* Check whether the specified issuer is configured as a MoodleNet instance that can be shared to.
*
* @param issuer $issuer The OAuth 2 issuer being validated.
* @return bool true if the issuer is enabled and available to share to.
*/
public static function is_valid_instance(issuer $issuer): bool {
global $CFG;
$issuerid = $issuer->get('id');
$allowedissuer = get_config('moodlenet', 'oauthservice');
return ($CFG->enablesharingtomoodlenet && $issuerid == $allowedissuer && $issuer->get('enabled') &&
$issuer->get('servicetype') == 'moodlenet');
}
/**
* Check whether a user has the capabilities required to share activities or courses to MoodleNet.
*
* @param \core\context\course $coursecontext Course context where the activity or course would be shared from.
* @param int $userid The user ID being checked.
* @param string $type The type of resource being checked (either 'activity' or 'course').
* @return boolean
* @throws \coding_exception If an invalid resource type is provided.
*/
public static function can_user_share(\core\context\course $coursecontext, int $userid, string $type = 'activity'): bool {
if ($type === 'course') {
return (has_capability('moodle/moodlenet:sharecourse', $coursecontext, $userid) &&
has_capability('moodle/backup:backupcourse', $coursecontext, $userid));
} else if ($type === 'activity') {
return (has_capability('moodle/moodlenet:shareactivity', $coursecontext, $userid) &&
has_capability('moodle/backup:backupactivity', $coursecontext, $userid));
}
throw new \coding_exception('Invalid resource type');
}
/**
* Get the support url.
*
* @return string
*/
public static function get_support_url(): string {
global $CFG;
$supporturl = '';
if ($CFG->supportavailability && $CFG->supportavailability !== CONTACT_SUPPORT_DISABLED) {
if (!empty($CFG->supportpage)) {
$supporturl = $CFG->supportpage;
} else {
$supporturl = $CFG->wwwroot . '/user/contactsitesupport.php';
}
}
return $supporturl;
}
/**
* Check the user has a valid capability in any course they are enrolled in.
*
* @param int $userid The user id to query
* @return string Returns 'yes' if capability found, 'no' if not
*/
public static function does_user_have_capability_in_any_course(int $userid): string {
// We are checking this way because we are not always in the course context and need
// a way to retrieve the user's courses to see if any of them have the correct capability.
$capabilities = [
'moodle/moodlenet:sharecourse',
'moodle/moodlenet:shareactivity'
];
// We are using 'no' instead of false to avoid confusing a cache key
// that was not found (returns false) with a user who does not have the capablity.
$isallowed = 'no';
$cache = \cache::make('core', 'moodlenet_usercanshare');
$cachedvalue = $cache->get($userid);
if ($cachedvalue === false) {
foreach ($capabilities as $capability) {
// Find at least one course that contains a capability match.
$course = get_user_capability_course($capability, $userid, true, '', 'id', 1);
if (!empty($course)) {
// Set the cache to 'yes' and break out of the loop.
$isallowed = 'yes';
break;
}
}
$cache->set($userid, $isallowed);
} else {
$isallowed = $cachedvalue;
}
return $isallowed;
}
}
+5 -54
View File
@@ -53,7 +53,7 @@ class provider implements
* @return collection The collection object filled out with information about this component.
*/
public static function get_metadata(collection $collection): collection {
// Except for moodlenet_share_progress, these tables are really data about site configuration and not user data.
// Except for shortlink, these tables are really data about site configuration and not user data.
// The config_log includes information about which user performed a configuration change.
// The value and oldvalue may contain sensitive information such as accounts for service passwords..
@@ -126,22 +126,6 @@ class provider implements
'scopehash' => 'privacy:metadata:oauth2_refresh_token:scopehash'
], 'privacy:metadata:oauth2_refresh_token');
// The moodlenet_share_progress includes details of an attempted share of a resource to MoodleNet.
$collection->add_database_table('moodlenet_share_progress', [
'type' => 'privacy:metadata:moodlenet_share_progress:type',
'courseid' => 'privacy:metadata:moodlenet_share_progress:courseid',
'cmid' => 'privacy:metadata:moodlenet_share_progress:cmid',
'userid' => 'privacy:metadata:moodlenet_share_progress:userid',
'timecreated' => 'privacy:metadata:moodlenet_share_progress:timecreated',
'resourceurl' => 'privacy:metadata:moodlenet_share_progress:resourceurl',
'status' => 'privacy:metadata:moodlenet_share_progress:status',
], 'privacy:metadata:moodlenet_share_progress');
// This resourceurl field is an external link from MoodleNet.
$collection->add_external_location_link('moodlenet_share_progress', [
'resourceurl' => 'privacy:metadata:moodlenet_share_progress:resourceurl',
], 'privacy:metadata:moodlenet_share_progress');
// The shortlink table includes data that associates a user with a shortlink URL.
$collection->add_database_table('shortlink', [
'shortcode' => 'privacy:metadata:shortlink:shortcode',
@@ -163,15 +147,6 @@ class provider implements
public static function get_contexts_for_userid(int $userid): contextlist {
$contextlist = new contextlist();
// MoodleNet share progress uses the user context.
$sql = "SELECT ctx.id
FROM {context} ctx
JOIN {moodlenet_share_progress} msp ON ctx.instanceid = msp.userid
AND ctx.contextlevel = :contextlevel
WHERE msp.userid = :userid";
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
$contextlist->add_from_sql($sql, $params);
// Shortlink.
$sql = "SELECT ctx.id
FROM {context} ctx
@@ -192,16 +167,6 @@ class provider implements
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
// MoodleNet share progress uses the user context.
if ($context->contextlevel == CONTEXT_USER) {
// Get all distinct userids from the table.
$sql = "SELECT DISTINCT userid
FROM {moodlenet_share_progress}
WHERE userid = :userid";
$params = ['userid' => $context->instanceid];
$userlist->add_from_sql('userid', $sql, $params);
}
// Shortlink.
if ($context->contextlevel == CONTEXT_USER) {
// Get all distinct userids from the table.
@@ -219,16 +184,11 @@ class provider implements
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
// Except for moodlenet_share_progress and shortlink, none of the core tables should be exported.
// Except for shortlink, none of the core tables should be exported.
global $DB;
foreach ($contextlist as $context) {
if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $contextlist->get_user()->id) {
// Get the user's MoodleNet share progress data.
$sharedata = $DB->get_records('moodlenet_share_progress', ['userid' => $context->instanceid]);
$subcontext = get_string('privacy:metadata:moodlenet_share_progress', 'moodle');
writer::with_context($context)->export_data([$subcontext], (object) $sharedata);
// Get the user's shortlink data.
$shortlinkdata = $DB->get_records('shortlink', ['userid' => $context->instanceid]);
$subcontext = get_string('privacy:metadata:shortlink', 'moodle');
@@ -243,14 +203,9 @@ class provider implements
* @param \context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
// Except for moodlenet_share_progress and shortlink, none of the data from these tables should be deleted.
// Except for shortlink, none of the data from these tables should be deleted.
global $DB;
// MoodleNet share progress uses the user context.
if ($context->contextlevel == CONTEXT_USER) {
$DB->delete_records('moodlenet_share_progress', ['userid' => $context->instanceid]);
}
// Shortlink.
if ($context->contextlevel == CONTEXT_USER) {
$DB->delete_records('shortlink', ['userid' => $context->instanceid]);
@@ -263,15 +218,13 @@ class provider implements
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
// Except for moodlenet_share_progress and shortlink, none of the data from these tables should be deleted.
// Except for shortlink, none of the data from these tables should be deleted.
// Note: Although it may be tempting to delete the adhoc task data, do not do so.
// The delete process is run as an adhoc task.
global $DB;
foreach ($contextlist as $context) {
if ($context->contextlevel == CONTEXT_USER && $context->instanceid == $contextlist->get_user()->id) {
// MoodleNet share progress uses the user context.
$DB->delete_records('moodlenet_share_progress', ['userid' => $context->instanceid]);
// Shortlink.
$DB->delete_records('shortlink', ['userid' => $context->instanceid]);
}
@@ -284,7 +237,7 @@ class provider implements
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
// Except for moodlenet_share_progress and shortlink, none of the data from these tables should be deleted.
// Except for shortlink, none of the data from these tables should be deleted.
// Note: Although it may be tempting to delete the adhoc task data, do not do so.
// The delete process is run as an adhoc task.
global $DB;
@@ -296,8 +249,6 @@ class provider implements
}
if ($context->contextlevel == CONTEXT_USER) {
// MoodleNet share progress uses the user context.
$DB->delete_records('moodlenet_share_progress', ['userid' => $context->instanceid]);
// Shortlink.
$DB->delete_records('shortlink', ['userid' => $context->instanceid]);
}
-20
View File
@@ -2767,16 +2767,6 @@ $capabilities = array(
'archetypes' => [],
],
// Allow users to share activities to MoodleNet.
'moodle/moodlenet:shareactivity' => [
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => [
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW,
]
],
// Allow users to configure course communication rooms.
'moodle/course:configurecoursecommunication' => [
'captype' => 'write',
@@ -2787,16 +2777,6 @@ $capabilities = array(
]
],
// Allow users to share courses to MoodleNet.
'moodle/moodlenet:sharecourse' => [
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => [
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW,
]
],
// Allow users to edit course welcome messages.
'moodle/course:editcoursewelcomemessage' => [
'captype' => 'write',
-13
View File
@@ -592,19 +592,6 @@ $definitions = array(
'staticaccelerationsize' => 100,
],
// Cache if a user has the capability to share to MoodleNet.
'moodlenet_usercanshare' => [
'mode' => cache_store::MODE_SESSION,
'simplekeys' => true,
'simpledata' => true,
'ttl' => 1800,
'invalidationevents' => [
'changesincoursecat',
'changesincategoryenrolment',
'changesincourse',
],
],
// A theme has been used in context to override the default theme.
// Applies to user, cohort, category and course.
'theme_usedincontext' => [
-15
View File
@@ -4884,21 +4884,6 @@
<INDEX NAME="timemodified" UNIQUE="false" FIELDS="timemodified"/>
</INDEXES>
</TABLE>
<TABLE NAME="moodlenet_share_progress" COMMENT="Records MoodleNet share progress">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="type" TYPE="int" LENGTH="2" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="cmid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="resourceurl" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="status" TYPE="int" LENGTH="2" NOTNULL="false" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="stored_progress" COMMENT="Records for any long running tasks we want to poll for progress">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
-30
View File
@@ -3247,36 +3247,6 @@ $functions = array(
'type' => 'write',
'ajax' => true,
],
'core_moodlenet_send_activity' => [
'classname' => 'core\external\moodlenet_send_activity',
'description' => 'Send activity to MoodleNet',
'type' => 'read',
'ajax' => true,
],
'core_moodlenet_get_share_info_activity' => [
'classname' => 'core\external\moodlenet_get_share_info_activity',
'description' => 'Get information about an activity being shared',
'type' => 'read',
'ajax' => true,
],
'core_moodlenet_auth_check' => [
'classname' => 'core\external\moodlenet_auth_check',
'description' => 'Check a user has authorized for a given MoodleNet site',
'type' => 'write',
'ajax' => true,
],
'core_moodlenet_get_shared_course_info' => [
'classname' => 'core\external\moodlenet_get_shared_course_info',
'description' => 'Get information about an course being shared',
'type' => 'read',
'ajax' => true,
],
'core_moodlenet_send_course' => [
'classname' => 'core\external\moodlenet_send_course',
'description' => 'Send course to MoodleNet',
'type' => 'read',
'ajax' => true,
],
'core_output_poll_stored_progress' => [
'classname' => 'core\external\output\poll_stored_progress',
'methodname' => 'execute',
+31 -3
View File
@@ -1043,7 +1043,7 @@ function xmldb_main_upgrade($oldversion) {
// Handle custom params.
if (isset($action['settings']['modelextraparams'])) {
$modelsettings['custom']['modelextraparams'] = $action['settings']['modelextraparams'] ?? '';
// Handle known models and their keys.
// Handle known models and their keys.
} else if (isset($affectedkeys[$model])) {
foreach ($affectedkeys[$model] as $key) {
$modelsettings[$model][$key] = $action['settings'][$key] ?? '';
@@ -1445,7 +1445,7 @@ function xmldb_main_upgrade($oldversion) {
$dbman->add_field($table, $field);
}
// Define field area to be added to customfield_data.
// Define field area to be added to customfield_data.
$table = new xmldb_table('customfield_data');
$field = new xmldb_field('area', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, 'component');
@@ -1621,7 +1621,7 @@ function xmldb_main_upgrade($oldversion) {
}
if ($oldversion < 2025121900.01) {
// Define field nextversion to be added to question_bank_entries.
// Define field nextversion to be added to question_bank_entries.
$table = new xmldb_table('question_bank_entries');
$field = new xmldb_field('nextversion', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'ownerid');
@@ -1760,5 +1760,33 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2026022700.02);
}
if ($oldversion < 2026030600.01) {
// Remove MoodleNet outbound sharing functionality.
// Drop the moodlenet_share_progress table if it exists.
$table = new xmldb_table('moodlenet_share_progress');
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
// Remove the enablesharingtomoodlenet config setting.
unset_config('enablesharingtomoodlenet');
// Remove MoodleNet outbound OAuth2 configuration.
unset_config('oauthservice', 'moodlenet');
$issuerids = $DB->get_fieldset_select('oauth2_issuer', 'id', "servicetype = ?", ['moodlenet']);
if (!empty($issuerids)) {
$DB->delete_records_list('oauth2_endpoint', 'issuerid', $issuerids);
$DB->delete_records_list('oauth2_access_token', 'issuerid', $issuerids);
$DB->delete_records_list('oauth2_refresh_token', 'issuerid', $issuerids);
$DB->delete_records_list('oauth2_system_account', 'issuerid', $issuerids);
$DB->delete_records_list('oauth2_user_field_mapping', 'issuerid', $issuerids);
$DB->delete_records_list('oauth2_issuer', 'id', $issuerids);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2026030600.01);
}
return true;
}
@@ -1,60 +0,0 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core/moodlenet/send_activity_modal_base
Modal to send activity to MoodleNet
Example context (json):
{
"uniqid": 123456,
"activitytype": "assignment",
"activityname": "Test assignment",
"sharetype": "resource"
}
}}
<div class="modal moodle-has-zindex" data-region="modal-container" aria-hidden="true" role="dialog">
<div class="modal-dialog moodlenet-share-dialog" role="document" data-region="modal" aria-labelledby="{{uniqid}}-moodlenet-modal-title" tabindex="0">
<div class="modal-content">
<div class="modal-header no-border" data-region="header">
<div id="{{uniqid}}-moodlenet-modal-title" class="modal-title" data-region="title">
<h5 class="visually-hidden modal-title">{{#str}} moodlenet:sharetomoodlenet, moodle {{/str}}</h5>
<div class="d-flex moodlenet-share-moodlenetinfo">
<span class="moodlenet-logo">{{#pix}} moodlenet, moodle, {{#str}} moodlenet:sharetomoodlenet, moodle {{/str}} {{/pix}}</span>
<span class="moodlenet-title">{{#str}} moodlenet:sharetomoodlenet, moodle {{/str}}</span>
</div>
</div>
<button type="button" class="btn-close" data-action="hide" aria-label="{{#cleanstr}}closebuttontitle{{/cleanstr}}"></button>
</div>
<div class="modal-body" data-region="body">
<div class="moodlenet-share-modal-content">
<div class="moodlenet-share-activity-info">
<span class="moodlenet-activity-type text-uppercase">{{activitytype}}</span>
<span class="moodlenet-activity-name">{{activityname}}</span>
</div>
<hr class="moodlenet-share-activity-info-hr">
<div class="moodlenet-share-notice">
{{sharenotice}}
</div>
{{^fullsharing}}
<div class="moodlenet-share-total-activities fw-bold ps-2 ms-3">
{{selectedactivitiesnotice}}
</div>
{{/fullsharing}}
</div>
</div>
<div class="modal-footer" data-region="footer">
{{> core/moodlenet/send_activity_modal_footer_share }}
</div>
</div>
</div>
</div>
@@ -1,57 +0,0 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core/moodlenet/send_activity_modal_done
This template renders done state for share modal.
Example context (json):
{
"success": true,
"sitesupporturl": "https://moodle.test/contactsitesupport.php"
}
}}
<div class="moodlenet-share-modal-content text-center">
<div class="moodlenet-circle-status{{#success}} success{{/success}}{{^success}} fail{{/success}}">
<span class="status-icon pt-5{{#success}} text-success{{/success}}{{^success}} text-danger{{/success}}">
{{#success}}
{{#pix}} t/check, core {{/pix}}
{{/success}}
{{^success}}
{{#pix}} fp/cross, theme {{/pix}}
{{/success}}
</span>
<span class="status-text fw-bold h5 pt-5">
{{#success}}
{{#str}} moodlenet:sharesuccesstitle, moodle {{/str}}
{{/success}}
{{^success}}
{{#str}} moodlenet:sharefailtitle, moodle {{/str}}
{{/success}}
</span>
</div>
<div class="result pt-2">
<span class="d-block h6 fw-normal pt-1 result-text">
{{#success}}
{{#str}} moodlenet:sharesuccesstext, moodle {{/str}}
{{/success}}
{{^success}}
{{#sitesupporturl}}
{{#str}} moodlenet:sharefailtextwithsitesupport, moodle, {{sitesupporturl}} {{/str}}
{{/sitesupporturl}}
{{^sitesupporturl}}
{{#str}} moodlenet:sharefailtext, moodle {{/str}}
{{/sitesupporturl}}
{{/success}}
</span>
</div>
</div>
@@ -1,28 +0,0 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core/moodlenet/send_activity_modal_footer_share
Render the footer for sharing activity
Example context (json):
{
"server": "MoodleNet Local"
}
}}
<div class="moodlenet-share-to">
{{#str}} moodlenet:sharingto, moodle {{/str}}<span class="fw-bold">{{server}}</span>
</div>
<div class="moodlenet-action-buttons">
<button type="button" class="btn btn-secondary" data-action="cancel">{{#str}} cancel, moodle {{/str}}</button>
<button type="button" class="btn btn-primary" data-action="share">{{#str}} share, moodle {{/str}}</button>
</div>
@@ -1,26 +0,0 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core/moodlenet/send_activity_modal_footer_view
Render the footer for sharing activity
Example context (json):
{
"resourceurl": "https://moodlenet.test/draft/activity.mbz"
}
}}
<div class="moodlenet-action-buttons">
<a class="btn btn-primary" href="{{resourceurl}}" target="_blank" rel="noopener noreferrer">
{{#str}} moodlenet:gotomoodlenet, moodle {{/str}}
</a>
</div>
@@ -1,28 +0,0 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core/moodlenet/send_activity_modal_packaging
This template renders the packaging stage for MoodleNet.
Example context (json):
{
}
}}
<div class="moodlenet-share-modal-content text-center p-5">
<div class="fw-bold h3 p-2">{{#str}} moodlenet:sharingstatus, moodle {{/str}}</div>
<div class="h5 fw-normal p-2 test-share-large">{{#str}} moodlenet:sharinglargefile, moodle {{/str}}</div>
<div class="p-5">
{{> core/loading }}
</div>
<div class="h5 fw-normal p-2">{{#str}} moodlenet:packagingandsending, moodle {{/str}}</div>
</div>
-56
View File
@@ -1,56 +0,0 @@
<?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/>.
/**
* View the progress of MoodleNet shares.
*
* @package core
* @copyright 2023 David Woloszyn <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\moodlenet\utilities;
require_once('../config.php');
require_login();
if (isguestuser()) {
throw new \moodle_exception('noguest');
}
// Capability was not found.
if (utilities::does_user_have_capability_in_any_course($USER->id) === 'no') {
throw new \moodle_exception('nocapabilitytousethisservice');
}
$pageurl = $CFG->wwwroot . '/moodlenet/shareprogress.php';
$PAGE->set_url($pageurl);
$PAGE->set_context(context_system::instance());
$PAGE->set_title(get_string('moodlenet:shareprogress'));
$PAGE->set_heading(get_string('moodlenet:shareprogress'));
$PAGE->set_pagelayout('standard');
echo $OUTPUT->header();
// Intro paragraph.
echo html_writer::div(get_string('moodlenet:shareprogressinfo'), 'mb-4');
// Build table.
$table = new core\moodlenet\share_progress_table('moodlenet-share-progress', $pageurl, $USER->id);
$perpage = $table->get_default_per_page();
$table->out($perpage, true);
echo $OUTPUT->footer();
+1 -1
View File
@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2026030600.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2026030600.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '5.2dev (Build: 20260306)'; // Human-friendly version name