Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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