Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 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  /**
  18   * This is the external API for this tool.
  19   *
  20   * @package    tool_mobile
  21   * @copyright  2016 Juan Leyva
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace tool_mobile;
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  require_once("$CFG->libdir/externallib.php");
  29  require_once("$CFG->dirroot/webservice/lib.php");
  30  
  31  use external_api;
  32  use external_files;
  33  use external_function_parameters;
  34  use external_value;
  35  use external_single_structure;
  36  use external_multiple_structure;
  37  use external_warnings;
  38  use context_system;
  39  use moodle_exception;
  40  use moodle_url;
  41  use core_text;
  42  use core_user;
  43  use coding_exception;
  44  
  45  /**
  46   * This is the external API for this tool.
  47   *
  48   * @copyright  2016 Juan Leyva
  49   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  50   */
  51  class external extends external_api {
  52  
  53      /**
  54       * Returns description of get_plugins_supporting_mobile() parameters.
  55       *
  56       * @return external_function_parameters
  57       * @since  Moodle 3.1
  58       */
  59      public static function get_plugins_supporting_mobile_parameters() {
  60          return new external_function_parameters(array());
  61      }
  62  
  63      /**
  64       * Returns a list of Moodle plugins supporting the mobile app.
  65       *
  66       * @return array an array of warnings and objects containing the plugin information
  67       * @since  Moodle 3.1
  68       */
  69      public static function get_plugins_supporting_mobile() {
  70          return array(
  71              'plugins' => api::get_plugins_supporting_mobile(),
  72              'warnings' => array(),
  73          );
  74      }
  75  
  76      /**
  77       * Returns description of get_plugins_supporting_mobile() result value.
  78       *
  79       * @return external_description
  80       * @since  Moodle 3.1
  81       */
  82      public static function get_plugins_supporting_mobile_returns() {
  83          return new external_single_structure(
  84              array(
  85                  'plugins' => new external_multiple_structure(
  86                      new external_single_structure(
  87                          array(
  88                              'component' => new external_value(PARAM_COMPONENT, 'The plugin component name.'),
  89                              'version' => new external_value(PARAM_NOTAGS, 'The plugin version number.'),
  90                              'addon' => new external_value(PARAM_COMPONENT, 'The Mobile addon (package) name.'),
  91                              'dependencies' => new external_multiple_structure(
  92                                                  new external_value(PARAM_COMPONENT, 'Mobile addon name.'),
  93                                                  'The list of Mobile addons this addon depends on.'
  94                                                 ),
  95                              'fileurl' => new external_value(PARAM_URL, 'The addon package url for download
  96                                                              or empty if it doesn\'t exist.'),
  97                              'filehash' => new external_value(PARAM_RAW, 'The addon package hash or empty if it doesn\'t exist.'),
  98                              'filesize' => new external_value(PARAM_INT, 'The addon package size or empty if it doesn\'t exist.'),
  99                              'handlers' => new external_value(PARAM_RAW, 'Handlers definition (JSON)', VALUE_OPTIONAL),
 100                              'lang' => new external_value(PARAM_RAW, 'Language strings used by the handlers (JSON)', VALUE_OPTIONAL),
 101                          )
 102                      )
 103                  ),
 104                  'warnings' => new external_warnings(),
 105              )
 106          );
 107      }
 108  
 109      /**
 110       * Returns description of get_public_config() parameters.
 111       *
 112       * @return external_function_parameters
 113       * @since  Moodle 3.2
 114       */
 115      public static function get_public_config_parameters() {
 116          return new external_function_parameters(array());
 117      }
 118  
 119      /**
 120       * Returns a list of the site public settings, those not requiring authentication.
 121       *
 122       * @return array with the settings and warnings
 123       * @since  Moodle 3.2
 124       */
 125      public static function get_public_config() {
 126          $result = api::get_public_config();
 127          $result['warnings'] = array();
 128          return $result;
 129      }
 130  
 131      /**
 132       * Returns description of get_public_config() result value.
 133       *
 134       * @return external_description
 135       * @since  Moodle 3.2
 136       */
 137      public static function get_public_config_returns() {
 138          return new external_single_structure(
 139              array(
 140                  'wwwroot' => new external_value(PARAM_RAW, 'Site URL.'),
 141                  'httpswwwroot' => new external_value(PARAM_RAW, 'Site https URL (if httpslogin is enabled).'),
 142                  'sitename' => new external_value(PARAM_RAW, 'Site name.'),
 143                  'guestlogin' => new external_value(PARAM_INT, 'Whether guest login is enabled.'),
 144                  'rememberusername' => new external_value(PARAM_INT, 'Values: 0 for No, 1 for Yes, 2 for optional.'),
 145                  'authloginviaemail' => new external_value(PARAM_INT, 'Whether log in via email is enabled.'),
 146                  'registerauth' => new external_value(PARAM_PLUGIN, 'Authentication method for user registration.'),
 147                  'forgottenpasswordurl' => new external_value(PARAM_URL, 'Forgotten password URL.'),
 148                  'authinstructions' => new external_value(PARAM_RAW, 'Authentication instructions.'),
 149                  'authnoneenabled' => new external_value(PARAM_INT, 'Whether auth none is enabled.'),
 150                  'enablewebservices' => new external_value(PARAM_INT, 'Whether Web Services are enabled.'),
 151                  'enablemobilewebservice' => new external_value(PARAM_INT, 'Whether the Mobile service is enabled.'),
 152                  'maintenanceenabled' => new external_value(PARAM_INT, 'Whether site maintenance is enabled.'),
 153                  'maintenancemessage' => new external_value(PARAM_RAW, 'Maintenance message.'),
 154                  'logourl' => new external_value(PARAM_URL, 'The site logo URL', VALUE_OPTIONAL),
 155                  'compactlogourl' => new external_value(PARAM_URL, 'The site compact logo URL', VALUE_OPTIONAL),
 156                  'typeoflogin' => new external_value(PARAM_INT, 'The type of login. 1 for app, 2 for browser, 3 for embedded.'),
 157                  'launchurl' => new external_value(PARAM_URL, 'SSO login launch URL.', VALUE_OPTIONAL),
 158                  'mobilecssurl' => new external_value(PARAM_URL, 'Mobile custom CSS theme', VALUE_OPTIONAL),
 159                  'tool_mobile_disabledfeatures' => new external_value(PARAM_RAW, 'Disabled features in the app', VALUE_OPTIONAL),
 160                  'identityproviders' => new external_multiple_structure(
 161                      new external_single_structure(
 162                          array(
 163                              'name' => new external_value(PARAM_TEXT, 'The identity provider name.'),
 164                              'iconurl' => new external_value(PARAM_URL, 'The icon URL for the provider.'),
 165                              'url' => new external_value(PARAM_URL, 'The URL of the provider.'),
 166                          )
 167                      ),
 168                      'Identity providers', VALUE_OPTIONAL
 169                  ),
 170                  'country' => new external_value(PARAM_NOTAGS, 'Default site country', VALUE_OPTIONAL),
 171                  'agedigitalconsentverification' => new external_value(PARAM_BOOL, 'Whether age digital consent verification
 172                      is enabled.', VALUE_OPTIONAL),
 173                  'supportname' => new external_value(PARAM_NOTAGS, 'Site support contact name
 174                      (only if age verification is enabled).', VALUE_OPTIONAL),
 175                  'supportemail' => new external_value(PARAM_EMAIL, 'Site support contact email
 176                      (only if age verification is enabled).', VALUE_OPTIONAL),
 177                  'autolang' => new external_value(PARAM_INT, 'Whether to detect default language
 178                      from browser setting.', VALUE_OPTIONAL),
 179                  'lang' => new external_value(PARAM_LANG, 'Default language for the site.', VALUE_OPTIONAL),
 180                  'langmenu' => new external_value(PARAM_INT, 'Whether the language menu should be displayed.', VALUE_OPTIONAL),
 181                  'langlist' => new external_value(PARAM_RAW, 'Languages on language menu.', VALUE_OPTIONAL),
 182                  'locale' => new external_value(PARAM_RAW, 'Sitewide locale.', VALUE_OPTIONAL),
 183                  'tool_mobile_minimumversion' => new external_value(PARAM_NOTAGS, 'Minimum required version to access.',
 184                      VALUE_OPTIONAL),
 185                  'tool_mobile_iosappid' => new external_value(PARAM_ALPHANUM, 'iOS app\'s unique identifier.',
 186                      VALUE_OPTIONAL),
 187                  'tool_mobile_androidappid' => new external_value(PARAM_NOTAGS, 'Android app\'s unique identifier.',
 188                      VALUE_OPTIONAL),
 189                  'tool_mobile_setuplink' => new external_value(PARAM_URL, 'App download page.', VALUE_OPTIONAL),
 190                  'warnings' => new external_warnings(),
 191              )
 192          );
 193      }
 194  
 195      /**
 196       * Returns description of get_config() parameters.
 197       *
 198       * @return external_function_parameters
 199       * @since  Moodle 3.2
 200       */
 201      public static function get_config_parameters() {
 202          return new external_function_parameters(
 203              array(
 204                  'section' => new external_value(PARAM_ALPHANUMEXT, 'Settings section name.', VALUE_DEFAULT, ''),
 205              )
 206          );
 207      }
 208  
 209      /**
 210       * Returns a list of site settings, filtering by section.
 211       *
 212       * @param string $section settings section name
 213       * @return array with the settings and warnings
 214       * @since  Moodle 3.2
 215       */
 216      public static function get_config($section = '') {
 217  
 218          $params = self::validate_parameters(self::get_config_parameters(), array('section' => $section));
 219  
 220          $settings = api::get_config($params['section']);
 221          $result['settings'] = array();
 222          foreach ($settings as $name => $value) {
 223              $result['settings'][] = array(
 224                  'name' => $name,
 225                  'value' => $value,
 226              );
 227          }
 228  
 229          $result['warnings'] = array();
 230          return $result;
 231      }
 232  
 233      /**
 234       * Returns description of get_config() result value.
 235       *
 236       * @return external_description
 237       * @since  Moodle 3.2
 238       */
 239      public static function get_config_returns() {
 240          return new external_single_structure(
 241              array(
 242                  'settings' => new external_multiple_structure(
 243                      new external_single_structure(
 244                          array(
 245                              'name' => new external_value(PARAM_RAW, 'The name of the setting'),
 246                              'value' => new external_value(PARAM_RAW, 'The value of the setting'),
 247                          )
 248                      ),
 249                      'Settings'
 250                  ),
 251                  'warnings' => new external_warnings(),
 252              )
 253          );
 254      }
 255  
 256      /**
 257       * Returns description of get_autologin_key() parameters.
 258       *
 259       * @return external_function_parameters
 260       * @since  Moodle 3.2
 261       */
 262      public static function get_autologin_key_parameters() {
 263          return new external_function_parameters (
 264              array(
 265                  'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token, usually generated by login/token.php'),
 266              )
 267          );
 268      }
 269  
 270      /**
 271       * Creates an auto-login key for the current user. Is created only in https sites and is restricted by time and ip address.
 272       *
 273       * Please note that it only works if the request comes from the Moodle mobile or desktop app.
 274       *
 275       * @param string $privatetoken the user private token for validating the request
 276       * @return array with the settings and warnings
 277       * @since  Moodle 3.2
 278       */
 279      public static function get_autologin_key($privatetoken) {
 280          global $CFG, $DB, $USER;
 281  
 282          $params = self::validate_parameters(self::get_autologin_key_parameters(), array('privatetoken' => $privatetoken));
 283          $privatetoken = $params['privatetoken'];
 284  
 285          $context = context_system::instance();
 286  
 287          // We must toletare these two exceptions: forcepasswordchangenotice and usernotfullysetup.
 288          try {
 289              self::validate_context($context);
 290          } catch (moodle_exception $e) {
 291              if ($e->errorcode != 'usernotfullysetup' && $e->errorcode != 'forcepasswordchangenotice') {
 292                  // In case we receive a different exception, throw it.
 293                  throw $e;
 294              }
 295          }
 296  
 297          // Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
 298          // This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
 299          // is used by other PHP scripts that can be opened in any browser.
 300          if (!\core_useragent::is_moodle_app()) {
 301              throw new moodle_exception('apprequired', 'tool_mobile');
 302          }
 303          api::check_autologin_prerequisites($USER->id);
 304  
 305          if (isset($_GET['privatetoken']) or empty($privatetoken)) {
 306              throw new moodle_exception('invalidprivatetoken', 'tool_mobile');
 307          }
 308  
 309          // Check the request counter, we must limit the number of times the privatetoken is sent.
 310          // Between each request 6 minutes are required.
 311          $last = get_user_preferences('tool_mobile_autologin_request_last', 0, $USER);
 312          // Check if we must reset the count.
 313          $timenow = time();
 314          if ($timenow - $last < 6 * MINSECS) {
 315              throw new moodle_exception('autologinkeygenerationlockout', 'tool_mobile');
 316          }
 317          set_user_preference('tool_mobile_autologin_request_last', $timenow, $USER);
 318  
 319          // We are expecting a privatetoken linked to the current token being used.
 320          // This WS is only valid when using mobile services via REST (this is intended).
 321          $currenttoken = required_param('wstoken', PARAM_ALPHANUM);
 322          $conditions = array(
 323              'userid' => $USER->id,
 324              'token' => $currenttoken,
 325              'privatetoken' => $privatetoken,
 326          );
 327          if (!$token = $DB->get_record('external_tokens', $conditions)) {
 328              throw new moodle_exception('invalidprivatetoken', 'tool_mobile');
 329          }
 330  
 331          $result = array();
 332          $result['key'] = api::get_autologin_key();
 333          $autologinurl = new moodle_url("/$CFG->admin/tool/mobile/autologin.php");
 334          $result['autologinurl'] = $autologinurl->out(false);
 335          $result['warnings'] = array();
 336          return $result;
 337      }
 338  
 339      /**
 340       * Returns description of get_autologin_key() result value.
 341       *
 342       * @return external_description
 343       * @since  Moodle 3.2
 344       */
 345      public static function get_autologin_key_returns() {
 346          return new external_single_structure(
 347              array(
 348                  'key' => new external_value(PARAM_ALPHANUMEXT, 'Auto-login key for a single usage with time expiration.'),
 349                  'autologinurl' => new external_value(PARAM_URL, 'Auto-login URL.'),
 350                  'warnings' => new external_warnings(),
 351              )
 352          );
 353      }
 354  
 355      /**
 356       * Returns description of get_content() parameters
 357       *
 358       * @return external_function_parameters
 359       * @since Moodle 3.5
 360       */
 361      public static function get_content_parameters() {
 362          return new external_function_parameters(
 363              array(
 364                  'component' => new external_value(PARAM_COMPONENT, 'Component where the class is e.g. mod_assign.'),
 365                  'method' => new external_value(PARAM_ALPHANUMEXT, 'Method to execute in class \$component\output\mobile.'),
 366                  'args' => new external_multiple_structure(
 367                      new external_single_structure(
 368                          array(
 369                              'name' => new external_value(PARAM_ALPHANUMEXT, 'Param name.'),
 370                              'value' => new external_value(PARAM_RAW, 'Param value.')
 371                          )
 372                      ), 'Args for the method are optional.', VALUE_OPTIONAL
 373                  )
 374              )
 375          );
 376      }
 377  
 378      /**
 379       * Returns a piece of content to be displayed in the Mobile app, it usually returns a template, javascript and
 380       * other structured data that will be used to render a view in the Mobile app.
 381       *
 382       * Callbacks (placed in \$component\output\mobile) that are called by this web service are responsible for doing the
 383       * appropriate security checks to access the information to be returned.
 384       *
 385       * @param string $component name of the component.
 386       * @param string $method function method name in class \$component\output\mobile.
 387       * @param array $args optional arguments for the method.
 388       * @return array HTML, JavaScript and other required data and information to create a view in the app.
 389       * @since Moodle 3.5
 390       * @throws coding_exception
 391       */
 392      public static function get_content($component, $method, $args = array()) {
 393          global $OUTPUT, $PAGE, $USER;
 394  
 395          $params = self::validate_parameters(self::get_content_parameters(),
 396              array(
 397                  'component' => $component,
 398                  'method' => $method,
 399                  'args' => $args
 400              )
 401          );
 402  
 403          // Reformat arguments into something less unwieldy.
 404          $arguments = array();
 405          foreach ($params['args'] as $paramargument) {
 406              $arguments[$paramargument['name']] = $paramargument['value'];
 407          }
 408  
 409          // The component was validated via the PARAM_COMPONENT parameter type.
 410          $classname = '\\' . $params['component'] .'\output\mobile';
 411          if (!method_exists($classname, $params['method'])) {
 412              throw new coding_exception("Missing method in $classname");
 413          }
 414          $result = call_user_func_array(array($classname, $params['method']), array($arguments));
 415  
 416          // Populate otherdata.
 417          $otherdata = array();
 418          if (!empty($result['otherdata'])) {
 419              $result['otherdata'] = (array) $result['otherdata'];
 420              foreach ($result['otherdata'] as $name => $value) {
 421                  $otherdata[] = array(
 422                      'name' => $name,
 423                      'value' => $value
 424                  );
 425              }
 426          }
 427  
 428          return array(
 429              'templates'  => !empty($result['templates']) ? $result['templates'] : array(),
 430              'javascript' => !empty($result['javascript']) ? $result['javascript'] : '',
 431              'otherdata'  => $otherdata,
 432              'files'      => !empty($result['files']) ? $result['files'] : array(),
 433              'restrict'   => !empty($result['restrict']) ? $result['restrict'] : array(),
 434              'disabled'   => !empty($result['disabled']) ? true : false,
 435          );
 436      }
 437  
 438      /**
 439       * Returns description of get_content() result value
 440       *
 441       * @return array
 442       * @since Moodle 3.5
 443       */
 444      public static function get_content_returns() {
 445          return new external_single_structure(
 446              array(
 447                  'templates' => new external_multiple_structure(
 448                      new external_single_structure(
 449                          array(
 450                              'id' => new external_value(PARAM_TEXT, 'ID of the template.'),
 451                              'html' => new external_value(PARAM_RAW, 'HTML code.'),
 452                          )
 453                      ),
 454                      'Templates required by the generated content.'
 455                  ),
 456                  'javascript' => new external_value(PARAM_RAW, 'JavaScript code.'),
 457                  'otherdata' => new external_multiple_structure(
 458                      new external_single_structure(
 459                          array(
 460                              'name' => new external_value(PARAM_RAW, 'Field name.'),
 461                              'value' => new external_value(PARAM_RAW, 'Field value.')
 462                          )
 463                      ),
 464                      'Other data that can be used or manipulated by the template via 2-way data-binding.'
 465                  ),
 466                  'files' => new external_files('Files in the content.'),
 467                  'restrict' => new external_single_structure(
 468                      array(
 469                          'users' => new external_multiple_structure(
 470                              new external_value(PARAM_INT, 'user id'), 'List of allowed users.', VALUE_OPTIONAL
 471                          ),
 472                          'courses' => new external_multiple_structure(
 473                              new external_value(PARAM_INT, 'course id'), 'List of allowed courses.', VALUE_OPTIONAL
 474                          ),
 475                      ),
 476                      'Restrict this content to certain users or courses.'
 477                  ),
 478                  'disabled' => new external_value(PARAM_BOOL, 'Whether we consider this disabled or not.', VALUE_OPTIONAL),
 479              )
 480          );
 481      }
 482  
 483      /**
 484       * Returns description of method parameters
 485       *
 486       * @return external_function_parameters
 487       * @since Moodle 3.7
 488       */
 489      public static function call_external_functions_parameters() {
 490          return new external_function_parameters([
 491              'requests' => new external_multiple_structure(
 492                  new external_single_structure([
 493                      'function' => new external_value(PARAM_ALPHANUMEXT, 'Function name'),
 494                      'arguments' => new external_value(PARAM_RAW, 'JSON-encoded object with named arguments', VALUE_DEFAULT, '{}'),
 495                      'settingraw' => new external_value(PARAM_BOOL, 'Return raw text', VALUE_DEFAULT, false),
 496                      'settingfilter' => new external_value(PARAM_BOOL, 'Filter text', VALUE_DEFAULT, false),
 497                      'settingfileurl' => new external_value(PARAM_BOOL, 'Rewrite plugin file URLs', VALUE_DEFAULT, true),
 498                      'settinglang' => new external_value(PARAM_LANG, 'Session language', VALUE_DEFAULT, ''),
 499                  ])
 500              )
 501          ]);
 502      }
 503  
 504      /**
 505       * Call multiple external functions and return all responses.
 506       *
 507       * @param array $requests List of requests.
 508       * @return array Responses.
 509       * @since Moodle 3.7
 510       */
 511      public static function call_external_functions($requests) {
 512          global $SESSION;
 513  
 514          $params = self::validate_parameters(self::call_external_functions_parameters(), ['requests' => $requests]);
 515  
 516          // We need to check if the functions being called are included in the service of the current token.
 517          // This function only works when using mobile services via REST (this is intended).
 518          $webservicemanager = new \webservice;
 519          $token = $webservicemanager->get_user_ws_token(required_param('wstoken', PARAM_ALPHANUM));
 520  
 521          $settings = \external_settings::get_instance();
 522          $defaultlang = current_language();
 523          $responses = [];
 524  
 525          foreach ($params['requests'] as $request) {
 526              // Some external functions modify _GET or $_POST data, we need to restore the original data after each call.
 527              $originalget = fullclone($_GET);
 528              $originalpost = fullclone($_POST);
 529  
 530              // Set external settings and language.
 531              $settings->set_raw($request['settingraw']);
 532              $settings->set_filter($request['settingfilter']);
 533              $settings->set_fileurl($request['settingfileurl']);
 534              $settings->set_lang($request['settinglang']);
 535              $SESSION->lang = $request['settinglang'] ?: $defaultlang;
 536  
 537              // Parse arguments to an array, validation is done in external_api::call_external_function.
 538              $args = @json_decode($request['arguments'], true);
 539              if (!is_array($args)) {
 540                  $args = [];
 541              }
 542  
 543              if ($webservicemanager->service_function_exists($request['function'], $token->externalserviceid)) {
 544                  $response = external_api::call_external_function($request['function'], $args, false);
 545              } else {
 546                  // Function not included in the service, return an access exception.
 547                  $response = [
 548                      'error' => true,
 549                      'exception' => [
 550                          'errorcode' => 'accessexception',
 551                          'module' => 'webservice'
 552                      ]
 553                  ];
 554                  if (debugging('', DEBUG_DEVELOPER)) {
 555                      $response['exception']['debuginfo'] = 'Access to the function is not allowed.';
 556                  }
 557              }
 558  
 559              if (isset($response['data'])) {
 560                  $response['data'] = json_encode($response['data']);
 561              }
 562              if (isset($response['exception'])) {
 563                  $response['exception'] = json_encode($response['exception']);
 564              }
 565              $responses[] = $response;
 566  
 567              // Restore original $_GET and $_POST.
 568              $_GET = $originalget;
 569              $_POST = $originalpost;
 570  
 571              if ($response['error']) {
 572                  // Do not process the remaining requests.
 573                  break;
 574              }
 575          }
 576  
 577          return ['responses' => $responses];
 578      }
 579  
 580      /**
 581       * Returns description of method result value
 582       *
 583       * @return external_single_structure
 584       * @since Moodle 3.7
 585       */
 586      public static function call_external_functions_returns() {
 587          return new external_function_parameters([
 588              'responses' => new external_multiple_structure(
 589                  new external_single_structure([
 590                      'error' => new external_value(PARAM_BOOL, 'Whether an exception was thrown.'),
 591                      'data' => new external_value(PARAM_RAW, 'JSON-encoded response data', VALUE_OPTIONAL),
 592                      'exception' => new external_value(PARAM_RAW, 'JSON-encoed exception info', VALUE_OPTIONAL),
 593                  ])
 594               )
 595          ]);
 596      }
 597  
 598      /**
 599       * Returns description of get_tokens_for_qr_login() parameters.
 600       *
 601       * @return external_function_parameters
 602       * @since  Moodle 3.9
 603       */
 604      public static function get_tokens_for_qr_login_parameters() {
 605          return new external_function_parameters (
 606              [
 607                  'qrloginkey' => new external_value(PARAM_ALPHANUMEXT, 'The user key for validating the request.'),
 608                  'userid' => new external_value(PARAM_INT, 'The user the key belongs to.'),
 609              ]
 610          );
 611      }
 612  
 613      /**
 614       * Returns a WebService token (and private token) for QR login
 615       *
 616       * @param string $qrloginkey the user key generated and embedded into the QR code for validating the request
 617       * @param int $userid the user the key belongs to
 618       * @return array with the tokens and warnings
 619       * @since  Moodle 3.9
 620       */
 621      public static function get_tokens_for_qr_login($qrloginkey, $userid) {
 622          global $PAGE, $DB;
 623  
 624          $params = self::validate_parameters(self::get_tokens_for_qr_login_parameters(),
 625              ['qrloginkey' => $qrloginkey, 'userid' => $userid]);
 626  
 627          $context = context_system::instance();
 628          // We need this to make work the format text functions.
 629          $PAGE->set_context($context);
 630  
 631          $qrcodetype = get_config('tool_mobile', 'qrcodetype');
 632          if ($qrcodetype != api::QR_CODE_LOGIN) {
 633              throw new moodle_exception('qrcodedisabled', 'tool_mobile');
 634          }
 635  
 636          // Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
 637          // This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
 638          // is used by other PHP scripts that can be opened in any browser.
 639          if (!\core_useragent::is_moodle_app()) {
 640              throw new moodle_exception('apprequired', 'tool_mobile');
 641          }
 642          api::check_autologin_prerequisites($params['userid']);  // Checks https, avoid site admins using this...
 643  
 644          // Validate and delete the key.
 645          $key = validate_user_key($params['qrloginkey'], 'tool_mobile', null);
 646          delete_user_key('tool_mobile', $params['userid']);
 647  
 648          // Double check key belong to user.
 649          if ($key->userid != $params['userid']) {
 650              throw new moodle_exception('invalidkey');
 651          }
 652  
 653          // Key validated, check user.
 654          $user = core_user::get_user($key->userid, '*', MUST_EXIST);
 655          core_user::require_active_user($user, true, true);
 656  
 657          // Generate WS tokens.
 658          \core\session\manager::set_user($user);
 659  
 660          // Check if the service exists and is enabled.
 661          $service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, 'enabled' => 1]);
 662          if (empty($service)) {
 663              // will throw exception if no token found
 664              throw new moodle_exception('servicenotavailable', 'webservice');
 665          }
 666  
 667          // Get an existing token or create a new one.
 668          $token = external_generate_token_for_current_user($service);
 669          $privatetoken = $token->privatetoken; // Save it here, the next function removes it.
 670          external_log_token_request($token);
 671  
 672          $result = [
 673              'token' => $token->token,
 674              'privatetoken' => $privatetoken ?: '',
 675              'warnings' => [],
 676          ];
 677          return $result;
 678      }
 679  
 680      /**
 681       * Returns description of get_tokens_for_qr_login() result value.
 682       *
 683       * @return external_description
 684       * @since  Moodle 3.9
 685       */
 686      public static function get_tokens_for_qr_login_returns() {
 687          return new external_single_structure(
 688              [
 689                  'token' => new external_value(PARAM_ALPHANUM, 'A valid WebService token for the official mobile app service.'),
 690                  'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token used for auto-login processes.'),
 691                  'warnings' => new external_warnings(),
 692              ]
 693          );
 694      }
 695  
 696      /**
 697       * Returns description of validate_subscription_key() parameters.
 698       *
 699       * @return external_function_parameters
 700       * @since  Moodle 3.9
 701       */
 702      public static function validate_subscription_key_parameters() {
 703          return new external_function_parameters(
 704              [
 705                  'key' => new external_value(PARAM_RAW, 'Site subscription temporary key.'),
 706              ]
 707          );
 708      }
 709  
 710      /**
 711       * Check if the given site subscription key is valid
 712       *
 713       * @param string $key subscriptiion temporary key
 714       * @return array with the settings and warnings
 715       * @since  Moodle 3.9
 716       */
 717      public static function validate_subscription_key(string $key): array {
 718          global $CFG, $PAGE;
 719  
 720          $params = self::validate_parameters(self::validate_subscription_key_parameters(), ['key' => $key]);
 721  
 722          $context = context_system::instance();
 723          $PAGE->set_context($context);
 724  
 725          $validated = false;
 726          $sitesubscriptionkey = get_config('tool_mobile', 'sitesubscriptionkey');
 727          if (!empty($sitesubscriptionkey) && $CFG->enablemobilewebservice && empty($CFG->disablemobileappsubscription)) {
 728              $sitesubscriptionkey = json_decode($sitesubscriptionkey);
 729              $validated = time() < $sitesubscriptionkey->validuntil && $params['key'] === $sitesubscriptionkey->key;
 730              // Delete existing, even if not validated to enforce security and attacks prevention.
 731              unset_config('sitesubscriptionkey', 'tool_mobile');
 732          }
 733  
 734          return [
 735              'validated' => $validated,
 736              'warnings' => [],
 737          ];
 738      }
 739  
 740      /**
 741       * Returns description of validate_subscription_key() result value.
 742       *
 743       * @return external_description
 744       * @since  Moodle 3.9
 745       */
 746      public static function validate_subscription_key_returns() {
 747          return new external_single_structure(
 748              [
 749                  'validated' => new external_value(PARAM_BOOL, 'Whether the key is validated or not.'),
 750                  'warnings' => new external_warnings(),
 751              ]
 752          );
 753      }
 754  }