Differences Between: [Versions 402 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core\moodlenet; 18 19 use cm_info; 20 use core\event\moodlenet_resource_exported; 21 use core\oauth2\client; 22 use moodle_exception; 23 use stdClass; 24 use stored_file; 25 26 /** 27 * API for sharing Moodle LMS activities to MoodleNet instances. 28 * 29 * @package core 30 * @copyright 2023 Michael Hawkins <michaelh@moodle.com> 31 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 32 */ 33 class activity_sender { 34 /** 35 * @var int Backup share format - the content is being shared as a Moodle backup file. 36 */ 37 public const SHARE_FORMAT_BACKUP = 0; 38 39 /** 40 * @var int Maximum upload file size (1.07 GB). 41 */ 42 public const MAX_FILESIZE = 1070000000; 43 44 /** 45 * @var cm_info The context module info object for the activity being shared. 46 */ 47 protected cm_info $cminfo; 48 49 /** 50 * @var stdClass The course where the activity is located. 51 */ 52 protected stdClass $course; 53 54 /** 55 * Class constructor. 56 * 57 * @param int $cmid The course module ID of the activity being shared. 58 * @param int $userid The user ID who is sharing the activity. 59 * @param moodlenet_client $moodlenetclient The moodlenet_client object used to perform the share. 60 * @param client $oauthclient The OAuth 2 client for the MoodleNet instance. 61 * @param int $shareformat The data format to share in. Defaults to a Moodle backup (SHARE_FORMAT_BACKUP). 62 * @throws moodle_exception 63 */ 64 public function __construct( 65 int $cmid, 66 protected int $userid, 67 protected moodlenet_client $moodlenetclient, 68 protected client $oauthclient, 69 protected int $shareformat = self::SHARE_FORMAT_BACKUP, 70 ) { 71 [$this->course, $this->cminfo] = get_course_and_cm_from_cmid($cmid); 72 73 if (!in_array($shareformat, $this->get_allowed_share_formats())) { 74 throw new moodle_exception('moodlenet:invalidshareformat'); 75 } 76 } 77 78 /** 79 * Share an activity/resource to MoodleNet. 80 * 81 * @return array The HTTP response code from MoodleNet and the MoodleNet draft resource URL (URL empty string on fail). 82 * Format: ['responsecode' => 201, 'drafturl' => 'https://draft.mnurl/here'] 83 * @throws moodle_exception 84 */ 85 public function share_activity(): array { 86 global $DB; 87 88 $accesstoken = ''; 89 $resourceurl = ''; 90 $issuer = $this->oauthclient->get_issuer(); 91 92 // Check user can share to the requested MoodleNet instance. 93 $coursecontext = \core\context\course::instance($this->course->id); 94 $usercanshare = utilities::can_user_share($coursecontext, $this->userid); 95 96 if ($usercanshare && utilities::is_valid_instance($issuer) && $this->oauthclient->is_logged_in()) { 97 $accesstoken = $this->oauthclient->get_accesstoken()->token; 98 } 99 100 // Throw an exception if the user is not currently set up to be able to share to MoodleNet. 101 if (!$accesstoken) { 102 throw new moodle_exception('moodlenet:usernotconfigured'); 103 } 104 105 // Attempt to prepare and send the resource if validation has passed and we have an OAuth 2 token. 106 107 // Prepare file in requested format. 108 $filedata = $this->prepare_share_contents(); 109 110 // If we have successfully prepared a file to share of permitted size, share it to MoodleNet. 111 if (!empty($filedata)) { 112 // Avoid sending a file larger than the defined limit. 113 $filesize = $filedata->get_filesize(); 114 if ($filesize > self::MAX_FILESIZE) { 115 $filedata->delete(); 116 throw new moodle_exception('moodlenet:sharefilesizelimitexceeded', 'core', '', [ 117 'filesize' => $filesize, 118 'filesizelimit' => self::MAX_FILESIZE, 119 ]); 120 } 121 122 // MoodleNet only accept plaintext descriptions. 123 $resourcedescription = $this->get_resource_description($coursecontext); 124 125 $response = $this->moodlenetclient->create_resource_from_stored_file( 126 $filedata, 127 $this->cminfo->name, 128 $resourcedescription, 129 ); 130 $responsecode = $response->getStatusCode(); 131 132 $responsebody = json_decode($response->getBody()); 133 $resourceurl = $responsebody->homepage ?? ''; 134 135 // TODO: Store consumable information about completed share - to be completed in MDL-77296. 136 137 // Delete the generated file now it is no longer required. 138 // (It has either been sent, or failed - retries not currently supported). 139 $filedata->delete(); 140 } 141 142 // Log every attempt to share (and whether or not it was successful). 143 $this->log_event($coursecontext, $this->cminfo->id, $resourceurl, $responsecode); 144 145 return [ 146 'responsecode' => $responsecode, 147 'drafturl' => $resourceurl, 148 ]; 149 } 150 151 /** 152 * Prepare the data for sharing, in the format specified. 153 * 154 * @return stored_file 155 */ 156 protected function prepare_share_contents(): stored_file { 157 switch ($this->shareformat) { 158 case self::SHARE_FORMAT_BACKUP: 159 // If sharing the activity as a backup, prepare the packaged backup. 160 $packager = new activity_packager($this->cminfo, $this->userid); 161 return $packager->get_package(); 162 default: 163 throw new \coding_exception("Unknown share format: {$this->shareformat}'"); 164 }; 165 } 166 167 /** 168 * Log an event to the admin logs for an outbound share attempt. 169 * 170 * @param \context $coursecontext The course context being shared from. 171 * @param int $cmid The CMID of the activity being shared. 172 * @param string $resourceurl The URL of the draft resource if it was created. 173 * @param int $responsecode The HTTP response code describing the outcome of the attempt. 174 * @return void 175 */ 176 protected function log_event( 177 \core\context $coursecontext, 178 int $cmid, 179 string $resourceurl, 180 int $responsecode, 181 ): void { 182 $event = moodlenet_resource_exported::create([ 183 'context' => $coursecontext, 184 'other' => [ 185 'cmids' => [$cmid], 186 'resourceurl' => $resourceurl, 187 'success' => ($responsecode == 201), 188 ], 189 ]); 190 $event->trigger(); 191 } 192 193 /** 194 * Return the list of supported share formats. 195 * 196 * @return array Array of supported share format values. 197 */ 198 protected function get_allowed_share_formats(): array { 199 return [ 200 self::SHARE_FORMAT_BACKUP, 201 ]; 202 } 203 204 /** 205 * Fetch the description for the resource being created, in a supported text format. 206 * 207 * @param \context $coursecontext The course context being shared from. 208 * @return string Converted activity description. 209 */ 210 protected function get_resource_description( 211 \context $coursecontext, 212 ): string { 213 global $PAGE, $DB; 214 // We need to set the page context here because content_to_text and format_text will need the page context to work. 215 $PAGE->set_context($coursecontext); 216 217 $intro = $DB->get_record($this->cminfo->modname, ['id' => $this->cminfo->instance], 'intro, introformat', MUST_EXIST); 218 $processeddescription = strip_tags($intro->intro); 219 $processeddescription = content_to_text(format_text( 220 $processeddescription, 221 $intro->introformat, 222 ), $intro->introformat); 223 224 return $processeddescription; 225 } 226 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body