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.

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