Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   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 core\event\moodlenet_resource_exported;
  20  use core\oauth2\client;
  21  use moodle_exception;
  22  use stored_file;
  23  
  24  /**
  25   * API for sharing Moodle LMS courses to MoodleNet instances.
  26   *
  27   * @package   core
  28   * @copyright 2023 Safat Shahin <safat.shahin@moodle.com>
  29   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class course_sender extends resource_sender {
  32  
  33      /**
  34       * @var \core\context\course|false The course context.
  35       */
  36      protected \core\context\course|false $coursecontext;
  37  
  38      /**
  39       * Constructor for course sender.
  40       *
  41       * @param int $courseid The course ID of the course being shared
  42       * @param int $userid The user ID who is sharing the activity
  43       * @param moodlenet_client $moodlenetclient The moodlenet_client object used to perform the share
  44       * @param client $oauthclient The OAuth 2 client for the MoodleNet instance
  45       * @param int $shareformat The data format to share in. Defaults to a Moodle backup (SHARE_FORMAT_BACKUP)
  46       */
  47      public function __construct(
  48          int $courseid,
  49          protected int $userid,
  50          protected moodlenet_client $moodlenetclient,
  51          protected client $oauthclient,
  52          protected int $shareformat = self::SHARE_FORMAT_BACKUP,
  53      ) {
  54          parent::__construct($courseid, $userid, $moodlenetclient, $oauthclient, $shareformat);
  55          $this->course = get_course($courseid);
  56          $this->coursecontext = \core\context\course::instance($courseid);
  57          $this->packager = new course_packager($this->course, $this->userid);
  58      }
  59  
  60      /**
  61       * Share a course to MoodleNet.
  62       *
  63       * @return array The HTTP response code from MoodleNet and the MoodleNet draft resource URL (URL empty string on fail).
  64       *               Format: ['responsecode' => 201, 'drafturl' => 'https://draft.mnurl/here']
  65       */
  66      public function share_resource(): array {
  67  
  68          $accesstoken = '';
  69          $issuer = $this->oauthclient->get_issuer();
  70  
  71          // Check user can share to the requested MoodleNet instance.
  72          $usercanshare = utilities::can_user_share($this->coursecontext, $this->userid, 'course');
  73  
  74          if ($usercanshare && utilities::is_valid_instance($issuer) && $this->oauthclient->is_logged_in()) {
  75              $accesstoken = $this->oauthclient->get_accesstoken()->token;
  76          }
  77  
  78          // Throw an exception if the user is not currently set up to be able to share to MoodleNet.
  79          if (!$accesstoken) {
  80              throw new moodle_exception('moodlenet:usernotconfigured');
  81          }
  82  
  83          // Attempt to prepare and send the resource if validation has passed and we have an OAuth 2 token.
  84  
  85          // Prepare file in requested format.
  86          $filedata = $this->prepare_share_contents();
  87  
  88          // Avoid sending a file larger than the defined limit.
  89          $filesize = $filedata->get_filesize();
  90          if ($filesize > self::MAX_FILESIZE) {
  91              $filedata->delete();
  92              throw new moodle_exception('moodlenet:sharefilesizelimitexceeded', 'core', '', [
  93                  'filesize' => $filesize,
  94                  'filesizelimit' => self::MAX_FILESIZE,
  95              ]);
  96          }
  97  
  98          // MoodleNet only accept plaintext descriptions.
  99          $resourcedescription = $this->get_resource_description();
 100  
 101          $response = $this->moodlenetclient->create_resource_from_stored_file(
 102              $filedata,
 103              $this->course->fullname,
 104              $resourcedescription,
 105          );
 106          $responsecode = $response->getStatusCode();
 107  
 108          $responsebody = json_decode($response->getBody(), false, 512, JSON_THROW_ON_ERROR);
 109          $resourceurl = $responsebody->homepage ?? '';
 110  
 111          // Delete the generated file now it is no longer required.
 112          // (It has either been sent, or failed - retries not currently supported).
 113          $filedata->delete();
 114  
 115          // Log every attempt to share (and whether it was successful).
 116          $this->log_event($resourceurl, $responsecode);
 117  
 118          return [
 119              'responsecode' => $responsecode,
 120              'drafturl' => $resourceurl,
 121          ];
 122      }
 123  
 124      /**
 125       * Log an event to the admin logs for an outbound share attempt.
 126       *
 127       * @param string $resourceurl The URL of the draft resource if it was created
 128       * @param int $responsecode The HTTP response code describing the outcome of the attempt
 129       * @return void
 130       */
 131      protected function log_event(
 132          string $resourceurl,
 133          int $responsecode,
 134      ): void {
 135          $event = moodlenet_resource_exported::create([
 136              'context' => $this->coursecontext,
 137              'other' => [
 138                  'courseid' => [$this->course->id],
 139                  'resourceurl' => $resourceurl,
 140                  'success' => ($responsecode === 201),
 141              ],
 142          ]);
 143          $event->trigger();
 144      }
 145  
 146      /**
 147       * Return the list of supported share formats.
 148       *
 149       * @return array Array of supported share format values.
 150       */
 151      protected static function get_allowed_share_formats(): array {
 152          return [
 153              self::SHARE_FORMAT_BACKUP,
 154          ];
 155      }
 156  
 157      /**
 158       * Fetch the description for the resource being created, in a supported text format.
 159       *
 160       * @return string Converted course description.
 161       */
 162      protected function get_resource_description(): string {
 163          global $PAGE;
 164  
 165          // We need to set the page context here because content_to_text and format_text will need the page context to work.
 166          $PAGE->set_context($this->coursecontext);
 167  
 168          $processeddescription = strip_tags($this->course->summary);
 169          $processeddescription = content_to_text
 170          (
 171              format_text(
 172                  $processeddescription,
 173                  $this->course->summaryformat,
 174              ),
 175              $this->course->summaryformat
 176          );
 177  
 178          return $processeddescription;
 179      }
 180  }