Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 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                  'supportpage' => new external_value(PARAM_URL, 'Site support page link.', VALUE_OPTIONAL),
 178                  'supportavailability' => new external_value(PARAM_INT, 'Determines who has access to contact site support.',
 179                      VALUE_OPTIONAL),
 180                  'autolang' => new external_value(PARAM_INT, 'Whether to detect default language
 181                      from browser setting.', VALUE_OPTIONAL),
 182                  'lang' => new external_value(PARAM_LANG, 'Default language for the site.', VALUE_OPTIONAL),
 183                  'langmenu' => new external_value(PARAM_INT, 'Whether the language menu should be displayed.', VALUE_OPTIONAL),
 184                  'langlist' => new external_value(PARAM_RAW, 'Languages on language menu.', VALUE_OPTIONAL),
 185                  'locale' => new external_value(PARAM_RAW, 'Sitewide locale.', VALUE_OPTIONAL),
 186                  'tool_mobile_minimumversion' => new external_value(PARAM_NOTAGS, 'Minimum required version to access.',
 187                      VALUE_OPTIONAL),
 188                  'tool_mobile_iosappid' => new external_value(PARAM_ALPHANUM, 'iOS app\'s unique identifier.',
 189                      VALUE_OPTIONAL),
 190                  'tool_mobile_androidappid' => new external_value(PARAM_NOTAGS, 'Android app\'s unique identifier.',
 191                      VALUE_OPTIONAL),
 192                  'tool_mobile_setuplink' => new external_value(PARAM_URL, 'App download page.', VALUE_OPTIONAL),
 193                  'tool_mobile_qrcodetype' => new external_value(PARAM_INT, 'QR login configuration.', VALUE_OPTIONAL),
 194                  'warnings' => new external_warnings(),
 195              )
 196          );
 197      }
 198  
 199      /**
 200       * Returns description of get_config() parameters.
 201       *
 202       * @return external_function_parameters
 203       * @since  Moodle 3.2
 204       */
 205      public static function get_config_parameters() {
 206          return new external_function_parameters(
 207              array(
 208                  'section' => new external_value(PARAM_ALPHANUMEXT, 'Settings section name.', VALUE_DEFAULT, ''),
 209              )
 210          );
 211      }
 212  
 213      /**
 214       * Returns a list of site settings, filtering by section.
 215       *
 216       * @param string $section settings section name
 217       * @return array with the settings and warnings
 218       * @since  Moodle 3.2
 219       */
 220      public static function get_config($section = '') {
 221  
 222          $params = self::validate_parameters(self::get_config_parameters(), array('section' => $section));
 223  
 224          $settings = api::get_config($params['section']);
 225          $result['settings'] = array();
 226          foreach ($settings as $name => $value) {
 227              $result['settings'][] = array(
 228                  'name' => $name,
 229                  'value' => $value,
 230              );
 231          }
 232  
 233          $result['warnings'] = array();
 234          return $result;
 235      }
 236  
 237      /**
 238       * Returns description of get_config() result value.
 239       *
 240       * @return external_description
 241       * @since  Moodle 3.2
 242       */
 243      public static function get_config_returns() {
 244          return new external_single_structure(
 245              array(
 246                  'settings' => new external_multiple_structure(
 247                      new external_single_structure(
 248                          array(
 249                              'name' => new external_value(PARAM_RAW, 'The name of the setting'),
 250                              'value' => new external_value(PARAM_RAW, 'The value of the setting'),
 251                          )
 252                      ),
 253                      'Settings'
 254                  ),
 255                  'warnings' => new external_warnings(),
 256              )
 257          );
 258      }
 259  
 260      /**
 261       * Returns description of get_autologin_key() parameters.
 262       *
 263       * @return external_function_parameters
 264       * @since  Moodle 3.2
 265       */
 266      public static function get_autologin_key_parameters() {
 267          return new external_function_parameters (
 268              array(
 269                  'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token, usually generated by login/token.php'),
 270              )
 271          );
 272      }
 273  
 274      /**
 275       * Creates an auto-login key for the current user. Is created only in https sites and is restricted by time and ip address.
 276       *
 277       * Please note that it only works if the request comes from the Moodle mobile or desktop app.
 278       *
 279       * @param string $privatetoken the user private token for validating the request
 280       * @return array with the settings and warnings
 281       * @since  Moodle 3.2
 282       */
 283      public static function get_autologin_key($privatetoken) {
 284          global $CFG, $DB, $USER;
 285  
 286          $params = self::validate_parameters(self::get_autologin_key_parameters(), array('privatetoken' => $privatetoken));
 287          $privatetoken = $params['privatetoken'];
 288  
 289          $context = context_system::instance();
 290  
 291          // We must toletare these two exceptions: forcepasswordchangenotice and usernotfullysetup.
 292          try {
 293              self::validate_context($context);
 294          } catch (moodle_exception $e) {
 295              if ($e->errorcode != 'usernotfullysetup' && $e->errorcode != 'forcepasswordchangenotice') {
 296                  // In case we receive a different exception, throw it.
 297                  throw $e;
 298              }
 299          }
 300  
 301          // Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
 302          // This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
 303          // is used by other PHP scripts that can be opened in any browser.
 304          if (!\core_useragent::is_moodle_app()) {
 305              throw new moodle_exception('apprequired', 'tool_mobile');
 306          }
 307          api::check_autologin_prerequisites($USER->id);
 308  
 309          if (isset($_GET['privatetoken']) or empty($privatetoken)) {
 310              throw new moodle_exception('invalidprivatetoken', 'tool_mobile');
 311          }
 312  
 313          // Check the request counter, we must limit the number of times the privatetoken is sent.
 314          // Between each request 6 minutes are required.
 315          $last = get_user_preferences('tool_mobile_autologin_request_last', 0, $USER);
 316          // Check if we must reset the count.
 317          $mintimereq = get_config('tool_mobile', 'autologinmintimebetweenreq');
 318          $mintimereq = empty($mintimereq) ? 6 * MINSECS : $mintimereq;
 319          $timenow = time();
 320          if ($timenow - $last < $mintimereq) {
 321              $minutes = $mintimereq / MINSECS;
 322              throw new moodle_exception('autologinkeygenerationlockout', 'tool_mobile', '', $minutes);
 323          }
 324          set_user_preference('tool_mobile_autologin_request_last', $timenow, $USER);
 325  
 326          // We are expecting a privatetoken linked to the current token being used.
 327          // This WS is only valid when using mobile services via REST (this is intended).
 328          $currenttoken = required_param('wstoken', PARAM_ALPHANUM);
 329          $conditions = array(
 330              'userid' => $USER->id,
 331              'token' => $currenttoken,
 332              'privatetoken' => $privatetoken,
 333          );
 334          if (!$token = $DB->get_record('external_tokens', $conditions)) {
 335              throw new moodle_exception('invalidprivatetoken', 'tool_mobile');
 336          }
 337  
 338          $result = array();
 339          $result['key'] = api::get_autologin_key();
 340          $autologinurl = new moodle_url("/$CFG->admin/tool/mobile/autologin.php");
 341          $result['autologinurl'] = $autologinurl->out(false);
 342          $result['warnings'] = array();
 343          return $result;
 344      }
 345  
 346      /**
 347       * Returns description of get_autologin_key() result value.
 348       *
 349       * @return external_description
 350       * @since  Moodle 3.2
 351       */
 352      public static function get_autologin_key_returns() {
 353          return new external_single_structure(
 354              array(
 355                  'key' => new external_value(PARAM_ALPHANUMEXT, 'Auto-login key for a single usage with time expiration.'),
 356                  'autologinurl' => new external_value(PARAM_URL, 'Auto-login URL.'),
 357                  'warnings' => new external_warnings(),
 358              )
 359          );
 360      }
 361  
 362      /**
 363       * Returns description of get_content() parameters
 364       *
 365       * @return external_function_parameters
 366       * @since Moodle 3.5
 367       */
 368      public static function get_content_parameters() {
 369          return new external_function_parameters(
 370              array(
 371                  'component' => new external_value(PARAM_COMPONENT, 'Component where the class is e.g. mod_assign.'),
 372                  'method' => new external_value(PARAM_ALPHANUMEXT, 'Method to execute in class \$component\output\mobile.'),
 373                  'args' => new external_multiple_structure(
 374                      new external_single_structure(
 375                          array(
 376                              'name' => new external_value(PARAM_ALPHANUMEXT, 'Param name.'),
 377                              'value' => new external_value(PARAM_RAW, 'Param value.')
 378                          )
 379                      ), 'Args for the method are optional.', VALUE_OPTIONAL
 380                  )
 381              )
 382          );
 383      }
 384  
 385      /**
 386       * Returns a piece of content to be displayed in the Mobile app, it usually returns a template, javascript and
 387       * other structured data that will be used to render a view in the Mobile app.
 388       *
 389       * Callbacks (placed in \$component\output\mobile) that are called by this web service are responsible for doing the
 390       * appropriate security checks to access the information to be returned.
 391       *
 392       * @param string $component name of the component.
 393       * @param string $method function method name in class \$component\output\mobile.
 394       * @param array $args optional arguments for the method.
 395       * @return array HTML, JavaScript and other required data and information to create a view in the app.
 396       * @since Moodle 3.5
 397       * @throws coding_exception
 398       */
 399      public static function get_content($component, $method, $args = array()) {
 400          global $OUTPUT, $PAGE, $USER;
 401  
 402          $params = self::validate_parameters(self::get_content_parameters(),
 403              array(
 404                  'component' => $component,
 405                  'method' => $method,
 406                  'args' => $args
 407              )
 408          );
 409  
 410          // Reformat arguments into something less unwieldy.
 411          $arguments = array();
 412          foreach ($params['args'] as $paramargument) {
 413              $arguments[$paramargument['name']] = $paramargument['value'];
 414          }
 415  
 416          // The component was validated via the PARAM_COMPONENT parameter type.
 417          $classname = '\\' . $params['component'] .'\output\mobile';
 418          if (!method_exists($classname, $params['method'])) {
 419              throw new coding_exception("Missing method in $classname");
 420          }
 421          $result = call_user_func_array(array($classname, $params['method']), array($arguments));
 422  
 423          // Populate otherdata.
 424          $otherdata = array();
 425          if (!empty($result['otherdata'])) {
 426              $result['otherdata'] = (array) $result['otherdata'];
 427              foreach ($result['otherdata'] as $name => $value) {
 428                  $otherdata[] = array(
 429                      'name' => $name,
 430                      'value' => $value
 431                  );
 432              }
 433          }
 434  
 435          return array(
 436              'templates'  => !empty($result['templates']) ? $result['templates'] : array(),
 437              'javascript' => !empty($result['javascript']) ? $result['javascript'] : '',
 438              'otherdata'  => $otherdata,
 439              'files'      => !empty($result['files']) ? $result['files'] : array(),
 440              'restrict'   => !empty($result['restrict']) ? $result['restrict'] : array(),
 441              'disabled'   => !empty($result['disabled']) ? true : false,
 442          );
 443      }
 444  
 445      /**
 446       * Returns description of get_content() result value
 447       *
 448       * @return array
 449       * @since Moodle 3.5
 450       */
 451      public static function get_content_returns() {
 452          return new external_single_structure(
 453              array(
 454                  'templates' => new external_multiple_structure(
 455                      new external_single_structure(
 456                          array(
 457                              'id' => new external_value(PARAM_TEXT, 'ID of the template.'),
 458                              'html' => new external_value(PARAM_RAW, 'HTML code.'),
 459                          )
 460                      ),
 461                      'Templates required by the generated content.'
 462                  ),
 463                  'javascript' => new external_value(PARAM_RAW, 'JavaScript code.'),
 464                  'otherdata' => new external_multiple_structure(
 465                      new external_single_structure(
 466                          array(
 467                              'name' => new external_value(PARAM_RAW, 'Field name.'),
 468                              'value' => new external_value(PARAM_RAW, 'Field value.')
 469                          )
 470                      ),
 471                      'Other data that can be used or manipulated by the template via 2-way data-binding.'
 472                  ),
 473                  'files' => new external_files('Files in the content.'),
 474                  'restrict' => new external_single_structure(
 475                      array(
 476                          'users' => new external_multiple_structure(
 477                              new external_value(PARAM_INT, 'user id'), 'List of allowed users.', VALUE_OPTIONAL
 478                          ),
 479                          'courses' => new external_multiple_structure(
 480                              new external_value(PARAM_INT, 'course id'), 'List of allowed courses.', VALUE_OPTIONAL
 481                          ),
 482                      ),
 483                      'Restrict this content to certain users or courses.'
 484                  ),
 485                  'disabled' => new external_value(PARAM_BOOL, 'Whether we consider this disabled or not.', VALUE_OPTIONAL),
 486              )
 487          );
 488      }
 489  
 490      /**
 491       * Returns description of method parameters
 492       *
 493       * @return external_function_parameters
 494       * @since Moodle 3.7
 495       */
 496      public static function call_external_functions_parameters() {
 497          return new external_function_parameters([
 498              'requests' => new external_multiple_structure(
 499                  new external_single_structure([
 500                      'function' => new external_value(PARAM_ALPHANUMEXT, 'Function name'),
 501                      'arguments' => new external_value(PARAM_RAW, 'JSON-encoded object with named arguments', VALUE_DEFAULT, '{}'),
 502                      'settingraw' => new external_value(PARAM_BOOL, 'Return raw text', VALUE_DEFAULT, false),
 503                      'settingfilter' => new external_value(PARAM_BOOL, 'Filter text', VALUE_DEFAULT, false),
 504                      'settingfileurl' => new external_value(PARAM_BOOL, 'Rewrite plugin file URLs', VALUE_DEFAULT, true),
 505                      'settinglang' => new external_value(PARAM_LANG, 'Session language', VALUE_DEFAULT, ''),
 506                  ])
 507              )
 508          ]);
 509      }
 510  
 511      /**
 512       * Call multiple external functions and return all responses.
 513       *
 514       * @param array $requests List of requests.
 515       * @return array Responses.
 516       * @since Moodle 3.7
 517       */
 518      public static function call_external_functions($requests) {
 519          global $SESSION;
 520  
 521          $params = self::validate_parameters(self::call_external_functions_parameters(), ['requests' => $requests]);
 522  
 523          // We need to check if the functions being called are included in the service of the current token.
 524          // This function only works when using mobile services via REST (this is intended).
 525          $webservicemanager = new \webservice;
 526          $token = $webservicemanager->get_user_ws_token(required_param('wstoken', PARAM_ALPHANUM));
 527  
 528          $settings = \external_settings::get_instance();
 529          $defaultlang = current_language();
 530          $responses = [];
 531  
 532          foreach ($params['requests'] as $request) {
 533              // Some external functions modify _GET or $_POST data, we need to restore the original data after each call.
 534              $originalget = fullclone($_GET);
 535              $originalpost = fullclone($_POST);
 536  
 537              // Set external settings and language.
 538              $settings->set_raw($request['settingraw']);
 539              $settings->set_filter($request['settingfilter']);
 540              $settings->set_fileurl($request['settingfileurl']);
 541              $settings->set_lang($request['settinglang']);
 542              $SESSION->lang = $request['settinglang'] ?: $defaultlang;
 543  
 544              // Parse arguments to an array, validation is done in external_api::call_external_function.
 545              $args = @json_decode($request['arguments'], true);
 546              if (!is_array($args)) {
 547                  $args = [];
 548              }
 549  
 550              if ($webservicemanager->service_function_exists($request['function'], $token->externalserviceid)) {
 551                  $response = external_api::call_external_function($request['function'], $args, false);
 552              } else {
 553                  // Function not included in the service, return an access exception.
 554                  $response = [
 555                      'error' => true,
 556                      'exception' => [
 557                          'errorcode' => 'accessexception',
 558                          'module' => 'webservice'
 559                      ]
 560                  ];
 561                  if (debugging('', DEBUG_DEVELOPER)) {
 562                      $response['exception']['debuginfo'] = 'Access to the function is not allowed.';
 563                  }
 564              }
 565  
 566              if (isset($response['data'])) {
 567                  $response['data'] = json_encode($response['data']);
 568              }
 569              if (isset($response['exception'])) {
 570                  $response['exception'] = json_encode($response['exception']);
 571              }
 572              $responses[] = $response;
 573  
 574              // Restore original $_GET and $_POST.
 575              $_GET = $originalget;
 576              $_POST = $originalpost;
 577  
 578              if ($response['error']) {
 579                  // Do not process the remaining requests.
 580                  break;
 581              }
 582          }
 583  
 584          return ['responses' => $responses];
 585      }
 586  
 587      /**
 588       * Returns description of method result value
 589       *
 590       * @return external_single_structure
 591       * @since Moodle 3.7
 592       */
 593      public static function call_external_functions_returns() {
 594          return new external_function_parameters([
 595              'responses' => new external_multiple_structure(
 596                  new external_single_structure([
 597                      'error' => new external_value(PARAM_BOOL, 'Whether an exception was thrown.'),
 598                      'data' => new external_value(PARAM_RAW, 'JSON-encoded response data', VALUE_OPTIONAL),
 599                      'exception' => new external_value(PARAM_RAW, 'JSON-encoed exception info', VALUE_OPTIONAL),
 600                  ])
 601               )
 602          ]);
 603      }
 604  
 605      /**
 606       * Returns description of get_tokens_for_qr_login() parameters.
 607       *
 608       * @return external_function_parameters
 609       * @since  Moodle 3.9
 610       */
 611      public static function get_tokens_for_qr_login_parameters() {
 612          return new external_function_parameters (
 613              [
 614                  'qrloginkey' => new external_value(PARAM_ALPHANUMEXT, 'The user key for validating the request.'),
 615                  'userid' => new external_value(PARAM_INT, 'The user the key belongs to.'),
 616              ]
 617          );
 618      }
 619  
 620      /**
 621       * Returns a WebService token (and private token) for QR login
 622       *
 623       * @param string $qrloginkey the user key generated and embedded into the QR code for validating the request
 624       * @param int $userid the user the key belongs to
 625       * @return array with the tokens and warnings
 626       * @since  Moodle 3.9
 627       */
 628      public static function get_tokens_for_qr_login($qrloginkey, $userid) {
 629          global $PAGE, $DB;
 630  
 631          $params = self::validate_parameters(self::get_tokens_for_qr_login_parameters(),
 632              ['qrloginkey' => $qrloginkey, 'userid' => $userid]);
 633  
 634          $context = context_system::instance();
 635          // We need this to make work the format text functions.
 636          $PAGE->set_context($context);
 637  
 638          $qrcodetype = get_config('tool_mobile', 'qrcodetype');
 639          if ($qrcodetype != api::QR_CODE_LOGIN) {
 640              throw new moodle_exception('qrcodedisabled', 'tool_mobile');
 641          }
 642  
 643          // Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
 644          // This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
 645          // is used by other PHP scripts that can be opened in any browser.
 646          if (!\core_useragent::is_moodle_app()) {
 647              throw new moodle_exception('apprequired', 'tool_mobile');
 648          }
 649          api::check_autologin_prerequisites($params['userid']);  // Checks https, avoid site admins using this...
 650  
 651          // Validate and delete the key.
 652          $key = validate_user_key($params['qrloginkey'], 'tool_mobile', null);
 653          delete_user_key('tool_mobile', $params['userid']);
 654  
 655          // Double check key belong to user.
 656          if ($key->userid != $params['userid']) {
 657              throw new moodle_exception('invalidkey');
 658          }
 659  
 660          // Key validated, check user.
 661          $user = core_user::get_user($key->userid, '*', MUST_EXIST);
 662          core_user::require_active_user($user, true, true);
 663  
 664          // Generate WS tokens.
 665          \core\session\manager::set_user($user);
 666  
 667          // Check if the service exists and is enabled.
 668          $service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, 'enabled' => 1]);
 669          if (empty($service)) {
 670              // will throw exception if no token found
 671              throw new moodle_exception('servicenotavailable', 'webservice');
 672          }
 673  
 674          // Get an existing token or create a new one.
 675          $token = external_generate_token_for_current_user($service);
 676          $privatetoken = $token->privatetoken; // Save it here, the next function removes it.
 677          external_log_token_request($token);
 678  
 679          $result = [
 680              'token' => $token->token,
 681              'privatetoken' => $privatetoken ?: '',
 682              'warnings' => [],
 683          ];
 684          return $result;
 685      }
 686  
 687      /**
 688       * Returns description of get_tokens_for_qr_login() result value.
 689       *
 690       * @return external_description
 691       * @since  Moodle 3.9
 692       */
 693      public static function get_tokens_for_qr_login_returns() {
 694          return new external_single_structure(
 695              [
 696                  'token' => new external_value(PARAM_ALPHANUM, 'A valid WebService token for the official mobile app service.'),
 697                  'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token used for auto-login processes.'),
 698                  'warnings' => new external_warnings(),
 699              ]
 700          );
 701      }
 702  
 703      /**
 704       * Returns description of validate_subscription_key() parameters.
 705       *
 706       * @return external_function_parameters
 707       * @since  Moodle 3.9
 708       */
 709      public static function validate_subscription_key_parameters() {
 710          return new external_function_parameters(
 711              [
 712                  'key' => new external_value(PARAM_RAW, 'Site subscription temporary key.'),
 713              ]
 714          );
 715      }
 716  
 717      /**
 718       * Check if the given site subscription key is valid
 719       *
 720       * @param string $key subscriptiion temporary key
 721       * @return array with the settings and warnings
 722       * @since  Moodle 3.9
 723       */
 724      public static function validate_subscription_key(string $key): array {
 725          global $CFG, $PAGE;
 726  
 727          $params = self::validate_parameters(self::validate_subscription_key_parameters(), ['key' => $key]);
 728  
 729          $context = context_system::instance();
 730          $PAGE->set_context($context);
 731  
 732          $validated = false;
 733          $sitesubscriptionkey = get_config('tool_mobile', 'sitesubscriptionkey');
 734          if (!empty($sitesubscriptionkey) && $CFG->enablemobilewebservice && empty($CFG->disablemobileappsubscription)) {
 735              $sitesubscriptionkey = json_decode($sitesubscriptionkey);
 736              $validated = time() < $sitesubscriptionkey->validuntil && $params['key'] === $sitesubscriptionkey->key;
 737              // Delete existing, even if not validated to enforce security and attacks prevention.
 738              unset_config('sitesubscriptionkey', 'tool_mobile');
 739          }
 740  
 741          return [
 742              'validated' => $validated,
 743              'warnings' => [],
 744          ];
 745      }
 746  
 747      /**
 748       * Returns description of validate_subscription_key() result value.
 749       *
 750       * @return external_description
 751       * @since  Moodle 3.9
 752       */
 753      public static function validate_subscription_key_returns() {
 754          return new external_single_structure(
 755              [
 756                  'validated' => new external_value(PARAM_BOOL, 'Whether the key is validated or not.'),
 757                  'warnings' => new external_warnings(),
 758              ]
 759          );
 760      }
 761  }