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\external;
  18  
  19  use context_course;
  20  use core\http_client;
  21  use core\moodlenet\course_partial_sender;
  22  use core\moodlenet\course_sender;
  23  use core\moodlenet\moodlenet_client;
  24  use core\moodlenet\share_recorder;
  25  use core\moodlenet\utilities;
  26  use core\oauth2\api;
  27  use core_external\external_api;
  28  use core_external\external_function_parameters;
  29  use core_external\external_multiple_structure;
  30  use core_external\external_single_structure;
  31  use core_external\external_value;
  32  use core_external\external_warnings;
  33  use moodle_url;
  34  
  35  /**
  36   * The external API to send course to MoodleNet.
  37   *
  38   * @package    core
  39   * @copyright  2023 Safat Shahin <safat.shahin@gmail.com>
  40   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  41   */
  42  class moodlenet_send_course extends external_api {
  43  
  44      /**
  45       * Describes the parameters for sending the course.
  46       *
  47       * @return external_function_parameters
  48       */
  49      public static function execute_parameters(): external_function_parameters {
  50          return new external_function_parameters([
  51              'issuerid' => new external_value(PARAM_INT, 'OAuth 2 issuer ID', VALUE_REQUIRED),
  52              'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED),
  53              'shareformat' => new external_value(PARAM_INT, 'Share format', VALUE_REQUIRED),
  54              'cmids' => new external_multiple_structure(
  55                  new external_value(PARAM_INT, 'Course module id', VALUE_DEFAULT, null, NULL_ALLOWED),
  56                  'List for course module ids', VALUE_DEFAULT, []
  57              ),
  58          ]);
  59      }
  60  
  61      /**
  62       * External function to send the course to MoodleNet.
  63       *
  64       * @param int $issuerid The MoodleNet OAuth 2 issuer ID
  65       * @param int $courseid The course ID
  66       * @param int $shareformat The share format being used, as defined by \core\moodlenet\course_sender
  67       * @param array $cmids The course module ids
  68       * @return array
  69       */
  70      public static function execute(
  71          int $issuerid,
  72          int $courseid,
  73          int $shareformat,
  74          array $cmids = [],
  75      ): array {
  76          global $CFG, $USER;
  77  
  78          [
  79              'issuerid' => $issuerid,
  80              'courseid' => $courseid,
  81              'shareformat' => $shareformat,
  82              'cmids' => $cmids,
  83          ] = self::validate_parameters(self::execute_parameters(), [
  84              'issuerid' => $issuerid,
  85              'courseid' => $courseid,
  86              'shareformat' => $shareformat,
  87              'cmids' => $cmids,
  88          ]);
  89  
  90          // Partial sharing check.
  91          $ispartialsharing = count($cmids) > 0;
  92  
  93          // Check capability.
  94          $coursecontext = context_course::instance($courseid);
  95          $usercanshare = utilities::can_user_share($coursecontext, $USER->id, 'course');
  96          if (!$usercanshare) {
  97              return self::return_errors(
  98                  $courseid,
  99                  'errorpermission',
 100                  get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle'))
 101              );
 102          }
 103  
 104          // Check format.
 105          if ($shareformat !== course_sender::SHARE_FORMAT_BACKUP) {
 106              return self::return_errors(
 107                  $shareformat,
 108                  'errorinvalidformat',
 109                  get_string('invalidparameter', 'debug')
 110              );
 111          }
 112  
 113          if ($ispartialsharing) {
 114              // Check course modules in the course.
 115              $modinfo = get_fast_modinfo($courseid);
 116              $cms = $modinfo->get_cms();
 117              foreach ($cmids as $cmid) {
 118                  if (!array_key_exists($cmid, $cms)) {
 119                      return self::return_errors(
 120                          $cmid,
 121                          'errorinvalidcmids',
 122                          get_string('invalidparameter', 'debug')
 123                      );
 124                  }
 125              }
 126          }
 127  
 128          // Get the issuer.
 129          $issuer = api::get_issuer($issuerid);
 130          // Validate the issuer and check if it is enabled or not.
 131          if (!utilities::is_valid_instance($issuer)) {
 132              return self::return_errors(
 133                  $issuerid,
 134                  'errorissuernotenabled',
 135                  get_string('invalidparameter', 'debug')
 136              );
 137          }
 138  
 139          // Get the OAuth Client.
 140          if (!$oauthclient = api::get_user_oauth_client(
 141              $issuer,
 142              new moodle_url($CFG->wwwroot),
 143              moodlenet_client::API_SCOPE_CREATE_RESOURCE
 144          )) {
 145              return self::return_errors(
 146                  $issuerid,
 147                  'erroroauthclient',
 148                  get_string('invalidparameter', 'debug')
 149              );
 150          }
 151  
 152          // Check login state.
 153          if (!$oauthclient->is_logged_in()) {
 154              return self::return_errors(
 155                  $issuerid,
 156                  'erroroauthclient',
 157                  get_string('moodlenet:issuerisnotauthorized', 'moodle')
 158              );
 159          }
 160  
 161          // Get the HTTP Client.
 162          $client = new http_client();
 163  
 164          // Share course.
 165          try {
 166              // Record course share progress.
 167              $shareid = share_recorder::insert_share_progress(share_recorder::TYPE_COURSE, $USER->id, $courseid);
 168  
 169              $moodlenetclient = new moodlenet_client($client, $oauthclient);
 170              if ($ispartialsharing) {
 171                  $coursesender = new course_partial_sender($courseid, $USER->id, $moodlenetclient,
 172                      $oauthclient, $cmids, $shareformat);
 173              } else {
 174                  $coursesender = new course_sender($courseid, $USER->id, $moodlenetclient, $oauthclient, $shareformat);
 175              }
 176              $result = $coursesender->share_resource();
 177              if (empty($result['drafturl'])) {
 178  
 179                  share_recorder::update_share_progress($shareid, share_recorder::STATUS_ERROR);
 180  
 181                  return self::return_errors(
 182                      $result['responsecode'],
 183                      'errorsendingcourse',
 184                      get_string('moodlenet:cannotconnecttoserver', 'moodle')
 185                  );
 186              }
 187          } catch (\moodle_exception | \JsonException $e) {
 188  
 189              share_recorder::update_share_progress($shareid, share_recorder::STATUS_ERROR);
 190  
 191              return self::return_errors(
 192                  0,
 193                  'errorsendingcourse',
 194                  $e->getMessage()
 195              );
 196          }
 197  
 198          share_recorder::update_share_progress($shareid, share_recorder::STATUS_SENT, $result['drafturl']);
 199  
 200          return [
 201              'status' => true,
 202              'resourceurl' => $result['drafturl'],
 203              'warnings' => [],
 204          ];
 205      }
 206  
 207      /**
 208       * Describes the data returned from the external function.
 209       *
 210       * @return external_single_structure
 211       */
 212      public static function execute_returns(): external_single_structure {
 213          return new external_single_structure([
 214              'status' => new external_value(PARAM_BOOL, 'Status: true if success'),
 215              // We used PARAM_TEXT instead of PARAM_URL because the URL return from MoodleNet may contain some characters.
 216              // It does not match with PARAM_URL, but the URL still works.
 217              // Since we just show the response resource URL to the user for them to navigate to MoodleNet, it would be safe.
 218              'resourceurl' => new external_value(PARAM_TEXT, 'Resource URL from MoodleNet'),
 219              'warnings' => new external_warnings(),
 220          ]);
 221      }
 222  
 223      /**
 224       * Handle return error.
 225       *
 226       * @param int $itemid Item id
 227       * @param string $warningcode Warning code
 228       * @param string $message Message
 229       * @return array
 230       */
 231      protected static function return_errors(int $itemid, string $warningcode, string $message): array {
 232          $warnings[] = [
 233              'item' => $itemid,
 234              'warningcode' => $warningcode,
 235              'message' => $message,
 236          ];
 237  
 238          return [
 239              'status' => false,
 240              'resourceurl' => '',
 241              'warnings' => $warnings,
 242          ];
 243      }
 244  }