MDL-75316 core: MoodleNet integration final improvements

This commit is contained in:
Michael Hawkins 2023-04-18 13:42:39 +08:00
parent 58ced4add0
commit 2e024f304a
16 changed files with 95 additions and 99 deletions

View File

@ -49,7 +49,7 @@ $returnurl = new moodle_url('/admin/moodlenet_oauth2_callback.php');
$returnurl->param('issuerid', $issuerid);
$returnurl->param('callback', 'yes');
$returnurl->param('sesskey', sesskey());
$oauthclient = api::get_user_oauth_client($issuer, $returnurl, moodlenet_client::API_SCOPE_CREATE, true);
$oauthclient = api::get_user_oauth_client($issuer, $returnurl, moodlenet_client::API_SCOPE_CREATE_RESOURCE, true);
$oauthclient->is_logged_in(); // Will upgrade the auth code to a token.
echo $OUTPUT->header();

View File

@ -426,6 +426,8 @@ $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'] = 'Invalid MoodleNet share format';
$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.';

View File

@ -6,6 +6,6 @@ define("core/moodlenet/send_resource",["exports","core/config","core/modal_facto
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_config=_interopRequireDefault(_config),_modal_factory=_interopRequireDefault(_modal_factory),_prefetch=_interopRequireDefault(_prefetch),Templates=_interopRequireWildcard(Templates),MoodleNetService=_interopRequireWildcard(MoodleNetService),_send_activity_modal=_interopRequireDefault(_send_activity_modal);let currentModal,siteSupportUrl,issuerId,courseId,cmId,shareFormat;const responseFromMoodleNet=function(status){let resourceUrl=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const $modal=currentModal.getModal(),modal=$modal[0];modal.querySelector(".modal-header").classList.add("no-border"),currentModal.setBody(Templates.render("core/moodlenet/send_activity_modal_done",{success:status,sitesupporturl:siteSupportUrl})),status&&(currentModal.setFooter(Templates.render("core/moodlenet/send_activity_modal_footer_view",{resourseurl:resourceUrl})),currentModal.showFooter())},handleAuthorization=issuerId=>MoodleNetService.authorizationCheck(issuerId,courseId).then((async data=>data.status?((issuerId,courseId,cmId,shareFormat)=>{const modal=currentModal.getModal()[0];modal.querySelector(".modal-header").classList.remove("no-border"),modal.querySelector(".modal-header").classList.add("no-header-text"),currentModal.setBody(Templates.render("core/moodlenet/send_activity_modal_packaging",{})),currentModal.hideFooter(),MoodleNetService.sendActivity(issuerId,courseId,cmId,shareFormat).then((async data=>{const status=data.status,resourceUrl=data.resourceurl;return responseFromMoodleNet(status,resourceUrl)})).catch(_notification.exception)})(issuerId,courseId,cmId,shareFormat):(window.moodleNetAuthorize=error=>{""==error?handleAuthorization(issuerId):"access_denied"!==error&&responseFromMoodleNet(!1,"")},window.open(data.loginurl,"moodlenet_auth","location=0,status=0,width=".concat(550,",height=").concat(550,",scrollbars=yes"))))).catch(_notification.exception);_exports.init=()=>{_prefetch.default.prefetchTemplates(["core/moodlenet/send_activity_modal_base","core/moodlenet/send_activity_modal_packaging","core/moodlenet/send_activity_modal_done","core/moodlenet/send_activity_modal_footer_view"]),document.addEventListener("click",(e=>{const shareAction=e.target.closest('[data-action="sendtomoodlenet"]'),sendAction=e.target.closest('.moodlenet-action-buttons [data-action="share"]');if(shareAction){e.preventDefault();const type=shareAction.getAttribute("data-type"),shareType=shareAction.getAttribute("data-sharetype"),cmId=_config.default.contextInstanceId;"activity"==type&&MoodleNetService.getActivityInformation(cmId).then((async data=>data.status?(siteSupportUrl=data.supportpageurl,issuerId=data.issuerid,_modal_factory.default.create({type:_send_activity_modal.default.TYPE,large:!0,templateContext:{activitytype:data.type,activityname:data.name,sharetype:await(0,_str.get_string)("moodlenet:sharetype"+shareType,"moodle"),server:data.server}}).then((modal=>(currentModal=modal,modal.show(),modal))).catch(_notification.exception)):(0,_notification.addNotification)({message:data.warnings[0].message,type:"error"}))).catch(_notification.exception)}sendAction&&(e.preventDefault(),courseId=_config.default.courseId,cmId=_config.default.contextInstanceId,shareFormat=0,handleAuthorization(issuerId))}))}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_config=_interopRequireDefault(_config),_modal_factory=_interopRequireDefault(_modal_factory),_prefetch=_interopRequireDefault(_prefetch),Templates=_interopRequireWildcard(Templates),MoodleNetService=_interopRequireWildcard(MoodleNetService),_send_activity_modal=_interopRequireDefault(_send_activity_modal);let currentModal,siteSupportUrl,issuerId,courseId,cmId,shareFormat;const responseFromMoodleNet=function(status){let resourceUrl=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const $modal=currentModal.getModal(),modal=$modal[0];modal.querySelector(".modal-header").classList.add("no-border"),currentModal.setBody(Templates.render("core/moodlenet/send_activity_modal_done",{success:status,sitesupporturl:siteSupportUrl})),status&&(currentModal.setFooter(Templates.render("core/moodlenet/send_activity_modal_footer_view",{resourseurl:resourceUrl})),currentModal.showFooter())},handleAuthorization=issuerId=>MoodleNetService.authorizationCheck(issuerId,courseId).then((async data=>data.status?((issuerId,cmId,shareFormat)=>{const modal=currentModal.getModal()[0];modal.querySelector(".modal-header").classList.remove("no-border"),modal.querySelector(".modal-header").classList.add("no-header-text"),currentModal.setBody(Templates.render("core/moodlenet/send_activity_modal_packaging",{})),currentModal.hideFooter(),MoodleNetService.sendActivity(issuerId,cmId,shareFormat).then((async data=>{const status=data.status,resourceUrl=data.resourceurl;return responseFromMoodleNet(status,resourceUrl)})).catch(_notification.exception)})(issuerId,cmId,shareFormat):(window.moodleNetAuthorize=(error,errorDescription)=>{""==error?handleAuthorization(issuerId):"access_denied"!==error&&(0,_notification.alert)("Authorization error","Error: "+error+"<br><br>Error description: "+errorDescription,"Cancel")},window.open(data.loginurl,"moodlenet_auth","location=0,status=0,width=".concat(550,",height=").concat(550,",scrollbars=yes"))))).catch(_notification.exception);_exports.init=()=>{_prefetch.default.prefetchTemplates(["core/moodlenet/send_activity_modal_base","core/moodlenet/send_activity_modal_packaging","core/moodlenet/send_activity_modal_done","core/moodlenet/send_activity_modal_footer_view"]),document.addEventListener("click",(e=>{const shareAction=e.target.closest('[data-action="sendtomoodlenet"]'),sendAction=e.target.closest('.moodlenet-action-buttons [data-action="share"]');if(shareAction){e.preventDefault();const type=shareAction.getAttribute("data-type"),shareType=shareAction.getAttribute("data-sharetype"),cmId=_config.default.contextInstanceId;"activity"==type&&MoodleNetService.getActivityInformation(cmId).then((async data=>data.status?(siteSupportUrl=data.supportpageurl,issuerId=data.issuerid,_modal_factory.default.create({type:_send_activity_modal.default.TYPE,large:!0,templateContext:{activitytype:data.type,activityname:data.name,sharetype:await(0,_str.get_string)("moodlenet:sharetype"+shareType,"moodle"),server:data.server}}).then((modal=>(currentModal=modal,modal.show(),modal))).catch(_notification.exception)):(0,_notification.addNotification)({message:data.warnings[0].message,type:"error"}))).catch(_notification.exception)}sendAction&&(e.preventDefault(),courseId=_config.default.courseId,cmId=_config.default.contextInstanceId,shareFormat=0,handleAuthorization(issuerId))}))}}));
//# sourceMappingURL=send_resource.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,6 @@ define("core/moodlenet/service",["exports","core/ajax"],(function(_exports,_ajax
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.2
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.sendActivity=_exports.getActivityInformation=_exports.authorizationCheck=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getActivityInformation=cmId=>{const request={methodname:"core_moodlenet_get_share_info_activity",args:{cmid:cmId}};return _ajax.default.call([request])[0]};_exports.sendActivity=(issuerId,courseId,cmId,shareFormat)=>{const request={methodname:"core_moodlenet_send_activity",args:{issuerid:issuerId,courseid:courseId,cmid:cmId,shareformat:shareFormat}};return _ajax.default.call([request])[0]};_exports.authorizationCheck=(issuerId,courseId)=>{const request={methodname:"core_moodlenet_auth_check",args:{issuerid:issuerId,courseid:courseId}};return _ajax.default.call([request])[0]}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.sendActivity=_exports.getActivityInformation=_exports.authorizationCheck=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getActivityInformation=cmId=>{const request={methodname:"core_moodlenet_get_share_info_activity",args:{cmid:cmId}};return _ajax.default.call([request])[0]};_exports.sendActivity=(issuerId,cmId,shareFormat)=>{const request={methodname:"core_moodlenet_send_activity",args:{issuerid:issuerId,cmid:cmId,shareformat:shareFormat}};return _ajax.default.call([request])[0]};_exports.authorizationCheck=(issuerId,courseId)=>{const request={methodname:"core_moodlenet_auth_check",args:{issuerid:issuerId,courseid:courseId}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=service.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"service.min.js","sources":["../../src/moodlenet/service.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A javascript module to handle MoodleNet ajax actions.\n *\n * @module core/moodlenet/service\n * @copyright 2023 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.2\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Get the activity information by course module id.\n *\n * @param {Integer} cmId The course module id.\n * @return {promise}\n */\nexport const getActivityInformation = (cmId) => {\n const request = {\n methodname: 'core_moodlenet_get_share_info_activity',\n args: {\n cmid: cmId\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Send the activity to Moodlenet.\n *\n * @param {Integer} issuerId The OAuth 2 issuer ID.\n * @param {Integer} courseId The course ID.\n * @param {Integer} cmId The course module ID.\n * @param {Integer} shareFormat The share format.\n * @return {promise}\n */\nexport const sendActivity = (issuerId, courseId, cmId, shareFormat) => {\n const request = {\n methodname: 'core_moodlenet_send_activity',\n args: {\n issuerid: issuerId,\n courseid: courseId,\n cmid: cmId,\n shareformat: shareFormat,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Check if the user is already authorized with MoodleNet.\n *\n * @param {Integer} issuerId The OAuth 2 issuer ID.\n * @param {Integer} courseId The course ID.\n * @return {promise}\n */\nexport const authorizationCheck = (issuerId, courseId) => {\n const request = {\n methodname: 'core_moodlenet_auth_check',\n args: {\n issuerid: issuerId,\n courseid: courseId,\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["cmId","request","methodname","args","cmid","Ajax","call","issuerId","courseId","shareFormat","issuerid","courseid","shareformat"],"mappings":";;;;;;;;0OAgCuCA,aAC7BC,QAAU,CACZC,WAAY,yCACZC,KAAM,CACFC,KAAMJ,cAIPK,cAAKC,KAAK,CAACL,UAAU,0BAYJ,CAACM,SAAUC,SAAUR,KAAMS,qBAC7CR,QAAU,CACZC,WAAY,+BACZC,KAAM,CACFO,SAAUH,SACVI,SAAUH,SACVJ,KAAMJ,KACNY,YAAaH,qBAIdJ,cAAKC,KAAK,CAACL,UAAU,gCAUE,CAACM,SAAUC,kBACnCP,QAAU,CACZC,WAAY,4BACZC,KAAM,CACFO,SAAUH,SACVI,SAAUH,kBAIXH,cAAKC,KAAK,CAACL,UAAU"}
{"version":3,"file":"service.min.js","sources":["../../src/moodlenet/service.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A javascript module to handle MoodleNet ajax actions.\n *\n * @module core/moodlenet/service\n * @copyright 2023 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.2\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Get the activity information by course module id.\n *\n * @param {Integer} cmId The course module id.\n * @return {promise}\n */\nexport const getActivityInformation = (cmId) => {\n const request = {\n methodname: 'core_moodlenet_get_share_info_activity',\n args: {\n cmid: cmId\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Send the activity to Moodlenet.\n *\n * @param {Integer} issuerId The OAuth 2 issuer ID.\n * @param {Integer} cmId The course module ID.\n * @param {Integer} shareFormat The share format.\n * @return {promise}\n */\nexport const sendActivity = (issuerId, cmId, shareFormat) => {\n const request = {\n methodname: 'core_moodlenet_send_activity',\n args: {\n issuerid: issuerId,\n cmid: cmId,\n shareformat: shareFormat,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Check if the user is already authorized with MoodleNet.\n *\n * @param {Integer} issuerId The OAuth 2 issuer ID.\n * @param {Integer} courseId The course ID.\n * @return {promise}\n */\nexport const authorizationCheck = (issuerId, courseId) => {\n const request = {\n methodname: 'core_moodlenet_auth_check',\n args: {\n issuerid: issuerId,\n courseid: courseId,\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["cmId","request","methodname","args","cmid","Ajax","call","issuerId","shareFormat","issuerid","shareformat","courseId","courseid"],"mappings":";;;;;;;;0OAgCuCA,aAC7BC,QAAU,CACZC,WAAY,yCACZC,KAAM,CACFC,KAAMJ,cAIPK,cAAKC,KAAK,CAACL,UAAU,0BAWJ,CAACM,SAAUP,KAAMQ,qBACnCP,QAAU,CACZC,WAAY,+BACZC,KAAM,CACFM,SAAUF,SACVH,KAAMJ,KACNU,YAAaF,qBAIdH,cAAKC,KAAK,CAACL,UAAU,gCAUE,CAACM,SAAUI,kBACnCV,QAAU,CACZC,WAAY,4BACZC,KAAM,CACFM,SAAUF,SACVK,SAAUD,kBAIXN,cAAKC,KAAK,CAACL,UAAU"}

View File

@ -24,7 +24,7 @@
import Config from 'core/config';
import ModalFactory from 'core/modal_factory';
import {exception as displayException, addNotification} from 'core/notification';
import {alert as displayAlert, addNotification, exception as displayException} from 'core/notification';
import {get_string as getString} from 'core/str';
import Prefetch from "core/prefetch";
import * as Templates from 'core/templates';
@ -44,11 +44,10 @@ let shareFormat;
* Handle send to MoodleNet.
*
* @param {int} issuerId The OAuth 2 issuer ID.
* @param {int} courseId The course ID.
* @param {int} cmId The course module ID.
* @param {int} shareFormat The share format.
*/
const sendToMoodleNet = (issuerId, courseId, cmId, shareFormat) => {
const sendToMoodleNet = (issuerId, cmId, shareFormat) => {
const $modal = currentModal.getModal();
const modal = $modal[0];
modal.querySelector('.modal-header').classList.remove('no-border');
@ -57,7 +56,7 @@ const sendToMoodleNet = (issuerId, courseId, cmId, shareFormat) => {
currentModal.setBody(Templates.render('core/moodlenet/send_activity_modal_packaging', {}));
currentModal.hideFooter();
MoodleNetService.sendActivity(issuerId, courseId, cmId, shareFormat).then(async(data) => {
MoodleNetService.sendActivity(issuerId, cmId, shareFormat).then(async(data) => {
const status = data.status;
const resourceUrl = data.resourceurl;
return responseFromMoodleNet(status, resourceUrl);
@ -102,12 +101,16 @@ const handleAuthorization = (issuerId) => {
if (!data.status) {
// Not yet authorized.
// Declare moodleNetAuthorize variable, so we can call it later in the callback.
window.moodleNetAuthorize = (error) => {
window.moodleNetAuthorize = (error, errorDescription) => {
// This will be called by the callback after the authorization is successful.
if (error == '') {
handleAuthorization(issuerId);
} else if (error !== 'access_denied') {
responseFromMoodleNet(false, '');
displayAlert(
'Authorization error',
'Error: ' + error + '<br><br>Error description: ' + errorDescription,
'Cancel'
);
}
};
// Open the login url of the OAuth 2 issuer for user to login into MoodleNet and authorize.
@ -115,7 +118,7 @@ const handleAuthorization = (issuerId) => {
`location=0,status=0,width=${windowsizewidth},height=${windowsizeheight},scrollbars=yes`);
} else {
// Already authorized.
return sendToMoodleNet(issuerId, courseId, cmId, shareFormat);
return sendToMoodleNet(issuerId, cmId, shareFormat);
}
}).catch(displayException);
};

View File

@ -45,17 +45,15 @@ export const getActivityInformation = (cmId) => {
* Send the activity to Moodlenet.
*
* @param {Integer} issuerId The OAuth 2 issuer ID.
* @param {Integer} courseId The course ID.
* @param {Integer} cmId The course module ID.
* @param {Integer} shareFormat The share format.
* @return {promise}
*/
export const sendActivity = (issuerId, courseId, cmId, shareFormat) => {
export const sendActivity = (issuerId, cmId, shareFormat) => {
const request = {
methodname: 'core_moodlenet_send_activity',
args: {
issuerid: issuerId,
courseid: courseId,
cmid: cmId,
shareformat: shareFormat,
}

View File

@ -88,7 +88,7 @@ class moodlenet_auth_check extends external_api {
$returnurl->param('sesskey', sesskey());
// Get the OAuth Client.
if (!$oauthclient = api::get_user_oauth_client($issuer, $returnurl, moodlenet_client::API_SCOPE_CREATE, true)) {
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'));
}

View File

@ -47,7 +47,6 @@ class moodlenet_send_activity extends external_api {
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),
'cmid' => new external_value(PARAM_INT, 'Course module ID', VALUE_REQUIRED),
'shareformat' => new external_value(PARAM_INT, 'Share format', VALUE_REQUIRED),
]);
@ -57,29 +56,27 @@ class moodlenet_send_activity extends external_api {
* External function to send the activity to MoodleNet.
*
* @param int $issuerid The MoodleNet OAuth 2 issuer ID
* @param int $courseid The course ID that contains the activity which being shared
* @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 $courseid, int $cmid, int $shareformat): array {
public static function execute(int $issuerid, int $cmid, int $shareformat): array {
global $CFG, $USER;
[
'issuerid' => $issuerid,
'courseid' => $courseid,
'cmid' => $cmid,
'shareformat' => $shareformat,
] = self::validate_parameters(self::execute_parameters(), [
'issuerid' => $issuerid,
'courseid' => $courseid,
'cmid' => $cmid,
'shareformat' => $shareformat,
]);
// Check capability.
$coursecontext = context_course::instance($courseid);
[$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',
@ -102,7 +99,7 @@ class moodlenet_send_activity extends external_api {
if (!$oauthclient = api::get_user_oauth_client(
$issuer,
new moodle_url($CFG->wwwroot),
moodlenet_client::API_SCOPE_CREATE
moodlenet_client::API_SCOPE_CREATE_RESOURCE
)) {
return self::return_errors($issuerid, 'erroroauthclient', get_string('invalidparameter', 'debug'));
}
@ -116,9 +113,9 @@ class moodlenet_send_activity extends external_api {
$client = new http_client();
// Share activity.
$activitysender = new activity_sender($courseid, $cmid, $USER->id, $client, $oauthclient, $shareformat);
try {
$moodlenetclient = new moodlenet_client($client, $oauthclient);
$activitysender = new activity_sender($cmid, $USER->id, $moodlenetclient, $oauthclient, $shareformat);
$result = $activitysender->share_activity();
if (empty($result['drafturl'])) {
return self::return_errors($result['responsecode'], 'errorsendingactivity',

View File

@ -18,9 +18,9 @@ namespace core\moodlenet;
use cm_info;
use core\event\moodlenet_resource_exported;
use core\http_client;
use core\oauth2\client;
use moodle_exception;
use stdClass;
/**
* API for sharing Moodle LMS activities to MoodleNet instances.
@ -45,25 +45,34 @@ class activity_sender {
*/
protected cm_info $cminfo;
/**
* @var stdClass The course where the activity is located.
*/
protected stdClass $course;
/**
* Class constructor.
*
* @param int $courseid The course ID where the activity is located.
* @param int $cmid The course module ID of the activity being shared.
* @param int $userid The user ID who is sharing the activity.
* @param \core\http_client $httpclient the httpclient object being used to perform the share.
* @param \core\oauth2\client $oauthclient The OAuth 2 client for the MoodleNet instance.
* @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(
protected int $courseid,
int $cmid,
protected int $userid,
protected http_client $httpclient,
protected moodlenet_client $moodlenetclient,
protected client $oauthclient,
protected int $shareformat = self::SHARE_FORMAT_BACKUP
) {
$this->cminfo = get_fast_modinfo($courseid)->get_cm($cmid);
[$this->course, $this->cminfo] = get_course_and_cm_from_cmid($cmid);
if (!in_array($shareformat, $this->get_allowed_share_formats())) {
throw new moodle_exception('moodlenet:invalidshareformat');
}
}
/**
@ -81,7 +90,7 @@ class activity_sender {
$issuer = $this->oauthclient->get_issuer();
// Check user can share to the requested MoodleNet instance.
$coursecontext = \context_course::instance($this->courseid);
$coursecontext = \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()) {
@ -90,7 +99,7 @@ class activity_sender {
// 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', 'core');
throw new moodle_exception('moodlenet:usernotconfigured');
}
// Attempt to prepare and send the resource if validation has passed and we have an OAuth 2 token.
@ -121,13 +130,7 @@ class activity_sender {
['context' => $coursecontext]
);
$moodlenetclient = new moodlenet_client(
$this->httpclient,
$this->oauthclient,
$this->cminfo->name,
$resourcedescription
);
$response = $moodlenetclient->create_resource_from_file($filedata);
$response = $this->moodlenetclient->create_resource_from_file($filedata, $this->cminfo->name, $resourcedescription);
$responsecode = $response->getStatusCode();
$responsebody = json_decode($response->getBody());
@ -158,13 +161,11 @@ class activity_sender {
switch ($this->shareformat) {
case self::SHARE_FORMAT_BACKUP:
default:
// If sharing the activity as a backup, prepare the packaged backup.
$packager = new activity_packager($this->cminfo, $this->userid);
$filedata = $packager->get_package();
break;
default:
$filedata = [];
break;
};
return $filedata;
@ -195,4 +196,16 @@ class activity_sender {
]);
$event->trigger();
}
/**
* Return the list of supported share formats.
*
* @return array Array of supported share format values.
*/
protected function get_allowed_share_formats(): array {
return [
self::SHARE_FORMAT_BACKUP,
];
}
}

View File

@ -30,45 +30,43 @@ class moodlenet_client {
/**
* @var string MoodleNet resource creation endpoint URI.
*/
protected const API_CREATE_URI = '/.pkg/@moodlenet/ed-resource/basic/v1/create';
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 = '@moodlenet/ed-resource:write.own';
public const API_SCOPE_CREATE_RESOURCE = '@moodlenet/ed-resource:write.own';
/**
* Constructor.
*
* @param \core\http_client $httpclient The httpclient object being used to perform the share.
* @param \core\oauth2\client $oauthclient The OAuth 2 client for the MoodleNet site being shared to.
* @param string $resourcename The name of the resource being shared.
* @param string $resourcedescription A description of the resource being shared.
* @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,
protected string $resourcename,
protected string $resourcedescription
) {
// All properties promoted, nothing furthe required.
// All properties promoted, nothing further required.
}
/**
* Create a resource on MoodleNet which includes a file.
*
* @param array $filedata The file data in the format [storedfile => stored_file object, filecontents => raw file].
* @return TODO - what is this type and what is the format?
* @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_file(array $filedata) {
public function create_resource_from_file(array $filedata, string $resourcename, $resourcedescription) {
// 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_URI;
$apiurl = rtrim($moodleneturl, '/') . self::API_CREATE_RESOURCE_URI;
$requestdata = $this->prepare_file_share_request_data($filedata);
$requestdata = $this->prepare_file_share_request_data($filedata, $resourcename, $resourcedescription);
return $this->httpclient->request('POST', $apiurl, $requestdata);
@ -79,13 +77,11 @@ class moodlenet_client {
* This creates an array in the format used by \core\httpclient options to send a multipart request.
*
* @param array $filedata An array of data relating to the file being shared (as prepared by ::prepare_share_contents).
* @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(
array $filedata,
): array {
global $DB;
protected function prepare_file_share_request_data(array $filedata, string $resourcename, $resourcedescription): array {
return [
'headers' => [
'Authorization' => 'Bearer ' . $this->oauthclient->get_accesstoken()->token,
@ -94,8 +90,8 @@ class moodlenet_client {
[
'name' => 'metadata',
'contents' => json_encode([
'name' => $this->resourcename,
'description' => $this->resourcedescription,
'name' => $resourcename,
'description' => $resourcedescription,
]),
'headers' => [
'Content-Disposition' => 'form-data; name="."',

View File

@ -30,7 +30,7 @@ class utilities {
/**
* Check whether the specified issuer is configured as a MoodleNet instance that can be shared to.
*
* @param \core\oauth2\issuer $issuer The OAuth 2 issuer being validated.
* @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 {
@ -46,7 +46,7 @@ class utilities {
/**
* Check whether a user has the capabilities required to share activities from a given course to MoodleNet.
*
* @param \context_course $coursecontext Course context where the activity would be shared from.
* @param context_course $coursecontext Course context where the activity would be shared from.
* @param int $userid The user ID being checked.
* @return boolean
*/

View File

@ -59,7 +59,7 @@ class moodlenet_send_activity_test extends externallib_advanced_testcase {
$issuer = \core\moodlenet\helpers::get_mock_issuer(0);
// Test with the experimental flag off.
$result = moodlenet_send_activity::execute($issuer->get('id'), $course->id, $moduleinstance->cmid, 0);
$result = moodlenet_send_activity::execute($issuer->get('id'), $moduleinstance->cmid, 0);
$result = external_api::clean_returnvalue(moodlenet_send_activity::execute_returns(), $result);
$this->assertFalse($result['status']);
$this->assertNotEmpty($result['warnings']);
@ -68,7 +68,7 @@ class moodlenet_send_activity_test extends externallib_advanced_testcase {
$CFG->enablesharingtomoodlenet = true;
// Test with invalid format.
$result = moodlenet_send_activity::execute($issuer->get('id'), $course->id, $moduleinstance->cmid, 5);
$result = moodlenet_send_activity::execute($issuer->get('id'), $moduleinstance->cmid, 5);
$result = external_api::clean_returnvalue(moodlenet_send_activity::execute_returns(), $result);
$this->assertFalse($result['status']);
$this->assertNotEmpty($result['warnings']);
@ -76,7 +76,7 @@ class moodlenet_send_activity_test extends externallib_advanced_testcase {
// Test with the user does not have permission.
$this->setUser($user);
$result = moodlenet_send_activity::execute($issuer->get('id'), $course->id, $moduleinstance->cmid, 0);
$result = moodlenet_send_activity::execute($issuer->get('id'), $moduleinstance->cmid, 0);
$result = external_api::clean_returnvalue(moodlenet_send_activity::execute_returns(), $result);
$this->assertFalse($result['status']);
$this->assertNotEmpty($result['warnings']);
@ -85,7 +85,7 @@ class moodlenet_send_activity_test extends externallib_advanced_testcase {
$this->setAdminUser();
// Test with the issuer is not enabled.
$result = moodlenet_send_activity::execute($issuer->get('id'), $course->id, $moduleinstance->cmid, 0);
$result = moodlenet_send_activity::execute($issuer->get('id'), $moduleinstance->cmid, 0);
$result = external_api::clean_returnvalue(moodlenet_send_activity::execute_returns(), $result);
$this->assertFalse($result['status']);
$this->assertNotEmpty($result['warnings']);
@ -95,7 +95,7 @@ class moodlenet_send_activity_test extends externallib_advanced_testcase {
$issuer->set('enabled', 1);
$irecord = $issuer->to_record();
api::update_issuer($irecord);
$result = moodlenet_send_activity::execute($issuer->get('id'), $course->id, $moduleinstance->cmid, 0);
$result = moodlenet_send_activity::execute($issuer->get('id'), $moduleinstance->cmid, 0);
$result = external_api::clean_returnvalue(moodlenet_send_activity::execute_returns(), $result);
$this->assertFalse($result['status']);
$this->assertNotEmpty($result['warnings']);
@ -103,7 +103,7 @@ class moodlenet_send_activity_test extends externallib_advanced_testcase {
set_config('oauthservice', $issuer->get('id'), 'moodlenet');
// Test with the issuer not yet authorized.
$result = moodlenet_send_activity::execute($issuer->get('id'), $course->id, $moduleinstance->cmid, 0);
$result = moodlenet_send_activity::execute($issuer->get('id'), $moduleinstance->cmid, 0);
$result = external_api::clean_returnvalue(moodlenet_send_activity::execute_returns(), $result);
$this->assertFalse($result['status']);
$this->assertNotEmpty($result['warnings']);

View File

@ -31,8 +31,6 @@ use testing_data_generator;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once('helpers.php');
/**
@ -72,7 +70,7 @@ class activity_sender_test extends \advanced_testcase {
$this->moduleinstance = $this->generator->create_module('assign', ['course' => $this->course->id]);
$this->coursecontext = context_course::instance($this->course->id);
// Create mock issuer.
$this->issuer = \core\moodlenet\helpers::get_mock_issuer(1);
$this->issuer = helpers::get_mock_issuer(1);
// Create mock builder for OAuth2 client.
$mockbuilder = $this->getMockBuilder('core\oauth2\client');
$mockbuilder->onlyMethods(['get_issuer', 'is_logged_in', 'get_accesstoken']);
@ -91,50 +89,39 @@ class activity_sender_test extends \advanced_testcase {
global $USER;
$this->setAdminUser();
$httpclient = new http_client();
$moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient);
// Set get_file method accessibility.
$method = new ReflectionMethod(activity_sender::class, 'prepare_share_contents');
$method->setAccessible(true);
// Test with invalid share format.
$this->expectException(\moodle_exception::class);
$this->expectExceptionMessage(get_string('moodlenet:invalidshareformat', 'error'));
$package = $method->invoke(new activity_sender(
$this->course->id,
$this->moduleinstance->cmid,
$USER->id,
new http_client(),
$moodlenetclient,
$this->mockoauthclient,
random_int(5, 30)
));
$this->assertEmpty($package);
// Test with valid share format and invalid course.
$this->expectException('dml_missing_record_exception');
// Test with valid share format and invalid course module.
$package = $method->invoke(new activity_sender(
random_int(1, 30),
$this->moduleinstance->cmid,
$USER->id,
new http_client(),
$this->mockoauthclient,
activity_sender::SHARE_FORMAT_BACKUP
));
$this->assertEmpty($package);
// Test with valid share format, valid course and invalid course module.
$package = $method->invoke(new activity_sender(
$this->course->id,
random_int(5, 30),
$USER->id,
new http_client(),
$moodlenetclient,
$this->mockoauthclient,
activity_sender::SHARE_FORMAT_BACKUP
));
$this->assertEmpty($package);
// Test with valid share format, valid course and valid course module.
// Test with valid share format and valid course module.
$package = $method->invoke(new activity_sender(
$this->course->id,
$this->moduleinstance->cmid,
$USER->id,
new http_client(),
$moodlenetclient,
$this->mockoauthclient,
activity_sender::SHARE_FORMAT_BACKUP
));
@ -188,11 +175,11 @@ class activity_sender_test extends \advanced_testcase {
$sink = $this->redirectEvents();
// Create activity sender.
$moodlenetclient = new moodlenet_client($httpclient, $this->mockoauthclient);
$activitysender = new activity_sender(
$this->course->id,
$this->moduleinstance->cmid,
$USER->id,
$httpclient,
$moodlenetclient,
$this->mockoauthclient,
activity_sender::SHARE_FORMAT_BACKUP
);

View File

@ -30,7 +30,7 @@ class helpers {
* Create and return a mock MoodleNet issuer.
*
* @param int $enabled Whether the issuer is enabled.
* @return core\oauth2\issuer The issuer that has been created.
* @return issuer The issuer that has been created.
*/
public static function get_mock_issuer(int $enabled): issuer {
$record = (object) [