Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 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   * Class containing the external API functions functions for the Data Privacy tool.
  18   *
  19   * @package    tool_dataprivacy
  20   * @copyright  2018 Jun Pataleta
  21   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  namespace tool_dataprivacy;
  24  defined('MOODLE_INTERNAL') || die();
  25  
  26  require_once("$CFG->libdir/externallib.php");
  27  require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
  28  
  29  use coding_exception;
  30  use context_helper;
  31  use context_system;
  32  use context_user;
  33  use core\invalid_persistent_exception;
  34  use core\notification;
  35  use core_user;
  36  use dml_exception;
  37  use external_api;
  38  use external_description;
  39  use external_function_parameters;
  40  use external_multiple_structure;
  41  use external_single_structure;
  42  use external_value;
  43  use external_warnings;
  44  use invalid_parameter_exception;
  45  use moodle_exception;
  46  use required_capability_exception;
  47  use restricted_context_exception;
  48  use tool_dataprivacy\external\category_exporter;
  49  use tool_dataprivacy\external\data_request_exporter;
  50  use tool_dataprivacy\external\purpose_exporter;
  51  use tool_dataprivacy\output\data_registry_page;
  52  
  53  /**
  54   * Class external.
  55   *
  56   * The external API for the Data Privacy tool.
  57   *
  58   * @copyright  2017 Jun Pataleta
  59   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  60   */
  61  class external extends external_api {
  62  
  63      /**
  64       * Parameter description for cancel_data_request().
  65       *
  66       * @since Moodle 3.5
  67       * @return external_function_parameters
  68       */
  69      public static function cancel_data_request_parameters() {
  70          return new external_function_parameters([
  71              'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
  72          ]);
  73      }
  74  
  75      /**
  76       * Cancel a data request.
  77       *
  78       * @since Moodle 3.5
  79       * @param int $requestid The request ID.
  80       * @return array
  81       * @throws invalid_persistent_exception
  82       * @throws coding_exception
  83       * @throws invalid_parameter_exception
  84       * @throws restricted_context_exception
  85       */
  86      public static function cancel_data_request($requestid) {
  87          global $USER;
  88  
  89          $warnings = [];
  90          $params = external_api::validate_parameters(self::cancel_data_request_parameters(), [
  91              'requestid' => $requestid
  92          ]);
  93          $requestid = $params['requestid'];
  94  
  95          // Validate context and access to manage the registry.
  96          $context = context_user::instance($USER->id);
  97          self::validate_context($context);
  98  
  99          // Ensure the request exists.
 100          $select = 'id = :id AND (userid = :userid OR requestedby = :requestedby)';
 101          $params = ['id' => $requestid, 'userid' => $USER->id, 'requestedby' => $USER->id];
 102          $requests = data_request::get_records_select($select, $params);
 103          $requestexists = count($requests) === 1;
 104  
 105          $result = false;
 106          if ($requestexists) {
 107              $request = reset($requests);
 108              $datasubject = $request->get('userid');
 109  
 110              if ($datasubject !== $USER->id) {
 111                  // The user is not the subject. Check that they can cancel this request.
 112                  if (!api::can_create_data_request_for_user($datasubject)) {
 113                      $forusercontext = \context_user::instance($datasubject);
 114                      throw new required_capability_exception($forusercontext,
 115                              'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
 116                  }
 117              }
 118  
 119              // TODO: Do we want a request to be non-cancellable past a certain point? E.g. When it's already approved/processing.
 120              $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_CANCELLED);
 121          } else {
 122              $warnings[] = [
 123                  'item' => $requestid,
 124                  'warningcode' => 'errorrequestnotfound',
 125                  'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
 126              ];
 127          }
 128  
 129          return [
 130              'result' => $result,
 131              'warnings' => $warnings
 132          ];
 133      }
 134  
 135      /**
 136       * Parameter description for cancel_data_request().
 137       *
 138       * @since Moodle 3.5
 139       * @return external_description
 140       */
 141      public static function cancel_data_request_returns() {
 142          return new external_single_structure([
 143              'result' => new external_value(PARAM_BOOL, 'The processing result'),
 144              'warnings' => new external_warnings()
 145          ]);
 146      }
 147  
 148      /**
 149       * Parameter description for contact_dpo().
 150       *
 151       * @since Moodle 3.5
 152       * @return external_function_parameters
 153       */
 154      public static function contact_dpo_parameters() {
 155          return new external_function_parameters([
 156              'message' => new external_value(PARAM_TEXT, 'The user\'s message to the Data Protection Officer(s)', VALUE_REQUIRED)
 157          ]);
 158      }
 159  
 160      /**
 161       * Make a general enquiry to a DPO.
 162       *
 163       * @since Moodle 3.5
 164       * @param string $message The message to be sent to the DPO.
 165       * @return array
 166       * @throws coding_exception
 167       * @throws invalid_parameter_exception
 168       * @throws invalid_persistent_exception
 169       * @throws restricted_context_exception
 170       * @throws dml_exception
 171       * @throws moodle_exception
 172       */
 173      public static function contact_dpo($message) {
 174          global $USER;
 175  
 176          $warnings = [];
 177          $params = external_api::validate_parameters(self::contact_dpo_parameters(), [
 178              'message' => $message
 179          ]);
 180          $message = $params['message'];
 181  
 182          // Validate context.
 183          $userid = $USER->id;
 184          $context = context_user::instance($userid);
 185          self::validate_context($context);
 186  
 187          // Lodge the request.
 188          $datarequest = new data_request();
 189          // The user the request is being made for.
 190          $datarequest->set('userid', $userid);
 191          // The user making the request.
 192          $datarequest->set('requestedby', $userid);
 193          // Set status.
 194          $datarequest->set('status', api::DATAREQUEST_STATUS_PENDING);
 195          // Set request type.
 196          $datarequest->set('type', api::DATAREQUEST_TYPE_OTHERS);
 197          // Set request comments.
 198          $datarequest->set('comments', $message);
 199  
 200          // Store subject access request.
 201          $datarequest->create();
 202  
 203          // Get the list of the site Data Protection Officers.
 204          $dpos = api::get_site_dpos();
 205  
 206          // Email the data request to the Data Protection Officer(s)/Admin(s).
 207          $result = true;
 208          foreach ($dpos as $dpo) {
 209              $sendresult = api::notify_dpo($dpo, $datarequest);
 210              if (!$sendresult) {
 211                  $result = false;
 212                  $warnings[] = [
 213                      'item' => $dpo->id,
 214                      'warningcode' => 'errorsendingtodpo',
 215                      'message' => get_string('errorsendingmessagetodpo', 'tool_dataprivacy')
 216                  ];
 217              }
 218          }
 219  
 220          return [
 221              'result' => $result,
 222              'warnings' => $warnings
 223          ];
 224      }
 225  
 226      /**
 227       * Parameter description for contact_dpo().
 228       *
 229       * @since Moodle 3.5
 230       * @return external_description
 231       */
 232      public static function contact_dpo_returns() {
 233          return new external_single_structure([
 234              'result' => new external_value(PARAM_BOOL, 'The processing result'),
 235              'warnings' => new external_warnings()
 236          ]);
 237      }
 238  
 239      /**
 240       * Parameter description for mark_complete().
 241       *
 242       * @since Moodle 3.5.2
 243       * @return external_function_parameters
 244       */
 245      public static function mark_complete_parameters() {
 246          return new external_function_parameters([
 247              'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
 248          ]);
 249      }
 250  
 251      /**
 252       * Mark a user's general enquiry's status as complete.
 253       *
 254       * @since Moodle 3.5.2
 255       * @param int $requestid The request ID of the general enquiry.
 256       * @return array
 257       * @throws coding_exception
 258       * @throws invalid_parameter_exception
 259       * @throws invalid_persistent_exception
 260       * @throws restricted_context_exception
 261       * @throws dml_exception
 262       * @throws moodle_exception
 263       */
 264      public static function mark_complete($requestid) {
 265          global $USER;
 266  
 267          $warnings = [];
 268          $params = external_api::validate_parameters(self::mark_complete_parameters(), [
 269              'requestid' => $requestid,
 270          ]);
 271          $requestid = $params['requestid'];
 272  
 273          // Validate context and access to manage the registry.
 274          $context = context_system::instance();
 275          self::validate_context($context);
 276          api::check_can_manage_data_registry();
 277  
 278          $message = get_string('markedcomplete', 'tool_dataprivacy');
 279          // Update the data request record.
 280          if ($result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE, $USER->id, $message)) {
 281              // Add notification in the session to be shown when the page is reloaded on the JS side.
 282              notification::success(get_string('requestmarkedcomplete', 'tool_dataprivacy'));
 283          }
 284  
 285          return [
 286              'result' => $result,
 287              'warnings' => $warnings
 288          ];
 289      }
 290  
 291      /**
 292       * Parameter description for mark_complete().
 293       *
 294       * @since Moodle 3.5.2
 295       * @return external_description
 296       */
 297      public static function mark_complete_returns() {
 298          return new external_single_structure([
 299              'result' => new external_value(PARAM_BOOL, 'The processing result'),
 300              'warnings' => new external_warnings()
 301          ]);
 302      }
 303  
 304      /**
 305       * Parameter description for get_data_request().
 306       *
 307       * @since Moodle 3.5
 308       * @return external_function_parameters
 309       */
 310      public static function get_data_request_parameters() {
 311          return new external_function_parameters([
 312              'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
 313          ]);
 314      }
 315  
 316      /**
 317       * Fetch the details of a user's data request.
 318       *
 319       * @since Moodle 3.5
 320       * @param int $requestid The request ID.
 321       * @return array
 322       * @throws coding_exception
 323       * @throws dml_exception
 324       * @throws invalid_parameter_exception
 325       * @throws restricted_context_exception
 326       * @throws moodle_exception
 327       */
 328      public static function get_data_request($requestid) {
 329          global $PAGE;
 330  
 331          $warnings = [];
 332          $params = external_api::validate_parameters(self::get_data_request_parameters(), [
 333              'requestid' => $requestid
 334          ]);
 335          $requestid = $params['requestid'];
 336  
 337          // Validate context.
 338          $context = context_system::instance();
 339          self::validate_context($context);
 340          $requestpersistent = new data_request($requestid);
 341          require_capability('tool/dataprivacy:managedatarequests', $context);
 342  
 343          $exporter = new data_request_exporter($requestpersistent, ['context' => $context]);
 344          $renderer = $PAGE->get_renderer('tool_dataprivacy');
 345          $result = $exporter->export($renderer);
 346  
 347          return [
 348              'result' => $result,
 349              'warnings' => $warnings
 350          ];
 351      }
 352  
 353      /**
 354       * Parameter description for get_data_request().
 355       *
 356       * @since Moodle 3.5
 357       * @return external_description
 358       */
 359      public static function get_data_request_returns() {
 360          return new external_single_structure([
 361              'result' => data_request_exporter::get_read_structure(),
 362              'warnings' => new external_warnings()
 363          ]);
 364      }
 365  
 366      /**
 367       * Parameter description for approve_data_request().
 368       *
 369       * @since Moodle 3.5
 370       * @return external_function_parameters
 371       */
 372      public static function approve_data_request_parameters() {
 373          return new external_function_parameters([
 374              'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
 375          ]);
 376      }
 377  
 378      /**
 379       * Approve a data request.
 380       *
 381       * @since Moodle 3.5
 382       * @param int $requestid The request ID.
 383       * @return array
 384       * @throws coding_exception
 385       * @throws dml_exception
 386       * @throws invalid_parameter_exception
 387       * @throws restricted_context_exception
 388       * @throws moodle_exception
 389       */
 390      public static function approve_data_request($requestid) {
 391          $warnings = [];
 392          $params = external_api::validate_parameters(self::approve_data_request_parameters(), [
 393              'requestid' => $requestid
 394          ]);
 395          $requestid = $params['requestid'];
 396  
 397          // Validate context.
 398          $context = context_system::instance();
 399          self::validate_context($context);
 400          require_capability('tool/dataprivacy:managedatarequests', $context);
 401  
 402          // Ensure the request exists.
 403          $requestexists = data_request::record_exists($requestid);
 404  
 405          $result = false;
 406          if ($requestexists) {
 407              $result = api::approve_data_request($requestid);
 408  
 409              // Add notification in the session to be shown when the page is reloaded on the JS side.
 410              notification::success(get_string('requestapproved', 'tool_dataprivacy'));
 411          } else {
 412              $warnings[] = [
 413                  'item' => $requestid,
 414                  'warningcode' => 'errorrequestnotfound',
 415                  'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
 416              ];
 417          }
 418  
 419          return [
 420              'result' => $result,
 421              'warnings' => $warnings
 422          ];
 423      }
 424  
 425      /**
 426       * Parameter description for approve_data_request().
 427       *
 428       * @since Moodle 3.5
 429       * @return external_description
 430       */
 431      public static function approve_data_request_returns() {
 432          return new external_single_structure([
 433              'result' => new external_value(PARAM_BOOL, 'The processing result'),
 434              'warnings' => new external_warnings()
 435          ]);
 436      }
 437  
 438      /**
 439       * Parameter description for bulk_approve_data_requests().
 440       *
 441       * @since Moodle 3.5
 442       * @return external_function_parameters
 443       */
 444      public static function bulk_approve_data_requests_parameters() {
 445          return new external_function_parameters([
 446              'requestids' => new external_multiple_structure(
 447                  new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
 448              )
 449          ]);
 450      }
 451  
 452      /**
 453       * Bulk approve bulk data request.
 454       *
 455       * @since Moodle 3.5
 456       * @param array $requestids Array consisting the request ID's.
 457       * @return array
 458       * @throws coding_exception
 459       * @throws dml_exception
 460       * @throws invalid_parameter_exception
 461       * @throws restricted_context_exception
 462       * @throws moodle_exception
 463       */
 464      public static function bulk_approve_data_requests($requestids) {
 465          $warnings = [];
 466          $result = false;
 467          $params = external_api::validate_parameters(self::bulk_approve_data_requests_parameters(), [
 468              'requestids' => $requestids
 469          ]);
 470          $requestids = $params['requestids'];
 471  
 472          // Validate context.
 473          $context = context_system::instance();
 474          self::validate_context($context);
 475          require_capability('tool/dataprivacy:managedatarequests', $context);
 476  
 477          foreach ($requestids as $requestid) {
 478              // Ensure the request exists.
 479              $requestexists = data_request::record_exists($requestid);
 480  
 481              if ($requestexists) {
 482                  api::approve_data_request($requestid);
 483              } else {
 484                  $warnings[] = [
 485                      'item' => $requestid,
 486                      'warningcode' => 'errorrequestnotfound',
 487                      'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
 488                  ];
 489              }
 490          }
 491  
 492          if (empty($warnings)) {
 493              $result = true;
 494              // Add notification in the session to be shown when the page is reloaded on the JS side.
 495              notification::success(get_string('requestsapproved', 'tool_dataprivacy'));
 496          }
 497  
 498          return [
 499              'result' => $result,
 500              'warnings' => $warnings
 501          ];
 502      }
 503  
 504      /**
 505       * Parameter description for bulk_approve_data_requests().
 506       *
 507       * @since Moodle 3.5
 508       * @return external_description
 509       */
 510      public static function bulk_approve_data_requests_returns() {
 511          return new external_single_structure([
 512              'result' => new external_value(PARAM_BOOL, 'The processing result'),
 513              'warnings' => new external_warnings()
 514          ]);
 515      }
 516  
 517      /**
 518       * Parameter description for deny_data_request().
 519       *
 520       * @since Moodle 3.5
 521       * @return external_function_parameters
 522       */
 523      public static function deny_data_request_parameters() {
 524          return new external_function_parameters([
 525              'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
 526          ]);
 527      }
 528  
 529      /**
 530       * Deny a data request.
 531       *
 532       * @since Moodle 3.5
 533       * @param int $requestid The request ID.
 534       * @return array
 535       * @throws coding_exception
 536       * @throws dml_exception
 537       * @throws invalid_parameter_exception
 538       * @throws restricted_context_exception
 539       * @throws moodle_exception
 540       */
 541      public static function deny_data_request($requestid) {
 542          $warnings = [];
 543          $params = external_api::validate_parameters(self::deny_data_request_parameters(), [
 544              'requestid' => $requestid
 545          ]);
 546          $requestid = $params['requestid'];
 547  
 548          // Validate context.
 549          $context = context_system::instance();
 550          self::validate_context($context);
 551          require_capability('tool/dataprivacy:managedatarequests', $context);
 552  
 553          // Ensure the request exists.
 554          $requestexists = data_request::record_exists($requestid);
 555  
 556          $result = false;
 557          if ($requestexists) {
 558              $result = api::deny_data_request($requestid);
 559  
 560              // Add notification in the session to be shown when the page is reloaded on the JS side.
 561              notification::success(get_string('requestdenied', 'tool_dataprivacy'));
 562          } else {
 563              $warnings[] = [
 564                  'item' => $requestid,
 565                  'warningcode' => 'errorrequestnotfound',
 566                  'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
 567              ];
 568          }
 569  
 570          return [
 571              'result' => $result,
 572              'warnings' => $warnings
 573          ];
 574      }
 575  
 576      /**
 577       * Parameter description for deny_data_request().
 578       *
 579       * @since Moodle 3.5
 580       * @return external_description
 581       */
 582      public static function deny_data_request_returns() {
 583          return new external_single_structure([
 584              'result' => new external_value(PARAM_BOOL, 'The processing result'),
 585              'warnings' => new external_warnings()
 586          ]);
 587      }
 588  
 589      /**
 590       * Parameter description for bulk_deny_data_requests().
 591       *
 592       * @since Moodle 3.5
 593       * @return external_function_parameters
 594       */
 595      public static function bulk_deny_data_requests_parameters() {
 596          return new external_function_parameters([
 597              'requestids' => new external_multiple_structure(
 598                  new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
 599              )
 600          ]);
 601      }
 602  
 603      /**
 604       * Bulk deny data requests.
 605       *
 606       * @since Moodle 3.5
 607       * @param array $requestids Array consisting of request ID's.
 608       * @return array
 609       * @throws coding_exception
 610       * @throws dml_exception
 611       * @throws invalid_parameter_exception
 612       * @throws restricted_context_exception
 613       * @throws moodle_exception
 614       */
 615      public static function bulk_deny_data_requests($requestids) {
 616          $warnings = [];
 617          $result = false;
 618          $params = external_api::validate_parameters(self::bulk_deny_data_requests_parameters(), [
 619              'requestids' => $requestids
 620          ]);
 621          $requestids = $params['requestids'];
 622  
 623          // Validate context.
 624          $context = context_system::instance();
 625          self::validate_context($context);
 626          require_capability('tool/dataprivacy:managedatarequests', $context);
 627  
 628          foreach ($requestids as $requestid) {
 629              // Ensure the request exists.
 630              $requestexists = data_request::record_exists($requestid);
 631  
 632              if ($requestexists) {
 633                  api::deny_data_request($requestid);
 634              } else {
 635                  $warnings[] = [
 636                      'item' => $requestid,
 637                      'warningcode' => 'errorrequestnotfound',
 638                      'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
 639                  ];
 640              }
 641          }
 642  
 643          if (empty($warnings)) {
 644              $result = true;
 645              // Add notification in the session to be shown when the page is reloaded on the JS side.
 646              notification::success(get_string('requestsdenied', 'tool_dataprivacy'));
 647          }
 648  
 649          return [
 650              'result' => $result,
 651              'warnings' => $warnings
 652          ];
 653      }
 654  
 655      /**
 656       * Parameter description for bulk_deny_data_requests().
 657       *
 658       * @since Moodle 3.5
 659       * @return external_description
 660       */
 661      public static function bulk_deny_data_requests_returns() {
 662          return new external_single_structure([
 663              'result' => new external_value(PARAM_BOOL, 'The processing result'),
 664              'warnings' => new external_warnings()
 665          ]);
 666      }
 667  
 668      /**
 669       * Parameter description for get_data_request().
 670       *
 671       * @since Moodle 3.5
 672       * @return external_function_parameters
 673       */
 674      public static function get_users_parameters() {
 675          return new external_function_parameters([
 676              'query' => new external_value(PARAM_TEXT, 'The search query', VALUE_REQUIRED)
 677          ]);
 678      }
 679  
 680      /**
 681       * Fetch the details of a user's data request.
 682       *
 683       * @since Moodle 3.5
 684       * @param string $query The search request.
 685       * @return array
 686       * @throws required_capability_exception
 687       * @throws dml_exception
 688       * @throws invalid_parameter_exception
 689       * @throws restricted_context_exception
 690       */
 691      public static function get_users($query) {
 692          global $DB;
 693          $params = external_api::validate_parameters(self::get_users_parameters(), [
 694              'query' => $query
 695          ]);
 696          $query = $params['query'];
 697  
 698          // Validate context.
 699          $context = context_system::instance();
 700          self::validate_context($context);
 701          require_capability('tool/dataprivacy:managedatarequests', $context);
 702  
 703          $allusernames = get_all_user_name_fields(true);
 704          // Exclude admins and guest user.
 705          $excludedusers = array_keys(get_admins()) + [guest_user()->id];
 706          $sort = 'lastname ASC, firstname ASC';
 707          $fields = 'id,' . $allusernames;
 708  
 709          $extrafields = get_extra_user_fields($context);
 710          if (!empty($extrafields)) {
 711              $fields .= ',' . implode(',', $extrafields);
 712          }
 713  
 714          list($sql, $params) = users_search_sql($query, '', false, $extrafields, $excludedusers);
 715          $users = $DB->get_records_select('user', $sql, $params, $sort, $fields, 0, 30);
 716          $useroptions = [];
 717          foreach ($users as $user) {
 718              $useroption = (object)[
 719                  'id' => $user->id,
 720                  'fullname' => fullname($user)
 721              ];
 722              $useroption->extrafields = [];
 723              foreach ($extrafields as $extrafield) {
 724                  // Sanitize the extra fields to prevent potential XSS exploit.
 725                  $useroption->extrafields[] = (object)[
 726                      'name' => $extrafield,
 727                      'value' => s($user->$extrafield)
 728                  ];
 729              }
 730              $useroptions[$user->id] = $useroption;
 731          }
 732  
 733          return $useroptions;
 734      }
 735  
 736      /**
 737       * Parameter description for get_users().
 738       *
 739       * @since Moodle 3.5
 740       * @return external_description
 741       * @throws coding_exception
 742       */
 743      public static function get_users_returns() {
 744          return new external_multiple_structure(new external_single_structure(
 745              [
 746                  'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'),
 747                  'fullname' => new external_value(core_user::get_property_type('firstname'), 'The fullname of the user'),
 748                  'extrafields' => new external_multiple_structure(
 749                      new external_single_structure([
 750                              'name' => new external_value(PARAM_TEXT, 'Name of the extrafield.'),
 751                              'value' => new external_value(PARAM_TEXT, 'Value of the extrafield.')
 752                          ]
 753                      ), 'List of extra fields', VALUE_OPTIONAL
 754                  )
 755              ]
 756          ));
 757      }
 758  
 759      /**
 760       * Parameter description for create_purpose_form().
 761       *
 762       * @since Moodle 3.5
 763       * @return external_function_parameters
 764       */
 765      public static function create_purpose_form_parameters() {
 766          return new external_function_parameters([
 767              'jsonformdata' => new external_value(PARAM_RAW, 'The data to create the purpose, encoded as a json array')
 768          ]);
 769      }
 770  
 771      /**
 772       * Creates a data purpose from form data.
 773       *
 774       * @since Moodle 3.5
 775       * @param string $jsonformdata
 776       * @return array
 777       */
 778      public static function create_purpose_form($jsonformdata) {
 779          global $PAGE;
 780  
 781          $warnings = [];
 782  
 783          $params = external_api::validate_parameters(self::create_purpose_form_parameters(), [
 784              'jsonformdata' => $jsonformdata
 785          ]);
 786  
 787          // Validate context and access to manage the registry.
 788          self::validate_context(\context_system::instance());
 789          api::check_can_manage_data_registry();
 790  
 791          $serialiseddata = json_decode($params['jsonformdata']);
 792          $data = array();
 793          parse_str($serialiseddata, $data);
 794  
 795          $purpose = new \tool_dataprivacy\purpose(0);
 796          $mform = new \tool_dataprivacy\form\purpose(null, ['persistent' => $purpose], 'post', '', null, true, $data);
 797  
 798          $validationerrors = true;
 799          if ($validateddata = $mform->get_data()) {
 800              $purpose = api::create_purpose($validateddata);
 801              $validationerrors = false;
 802          } else if ($errors = $mform->is_validated()) {
 803              throw new moodle_exception('generalerror');
 804          }
 805  
 806          $exporter = new purpose_exporter($purpose, ['context' => \context_system::instance()]);
 807          return [
 808              'purpose' => $exporter->export($PAGE->get_renderer('core')),
 809              'validationerrors' => $validationerrors,
 810              'warnings' => $warnings
 811          ];
 812      }
 813  
 814      /**
 815       * Returns for create_purpose_form().
 816       *
 817       * @since Moodle 3.5
 818       * @return external_single_structure
 819       */
 820      public static function create_purpose_form_returns() {
 821          return new external_single_structure([
 822              'purpose' => purpose_exporter::get_read_structure(),
 823              'validationerrors' => new external_value(PARAM_BOOL, 'Were there validation errors', VALUE_REQUIRED),
 824              'warnings' => new external_warnings()
 825          ]);
 826      }
 827  
 828      /**
 829       * Parameter description for delete_purpose().
 830       *
 831       * @since Moodle 3.5
 832       * @return external_function_parameters
 833       */
 834      public static function delete_purpose_parameters() {
 835          return new external_function_parameters([
 836              'id' => new external_value(PARAM_INT, 'The purpose ID', VALUE_REQUIRED)
 837          ]);
 838      }
 839  
 840      /**
 841       * Deletes a data purpose.
 842       *
 843       * @since Moodle 3.5
 844       * @param int $id The ID.
 845       * @return array
 846       * @throws invalid_persistent_exception
 847       * @throws coding_exception
 848       * @throws invalid_parameter_exception
 849       */
 850      public static function delete_purpose($id) {
 851          global $USER;
 852  
 853          $params = external_api::validate_parameters(self::delete_purpose_parameters(), [
 854              'id' => $id
 855          ]);
 856  
 857          // Validate context and access to manage the registry.
 858          self::validate_context(\context_system::instance());
 859          api::check_can_manage_data_registry();
 860  
 861          $result = api::delete_purpose($params['id']);
 862  
 863          return [
 864              'result' => $result,
 865              'warnings' => []
 866          ];
 867      }
 868  
 869      /**
 870       * Parameter description for delete_purpose().
 871       *
 872       * @since Moodle 3.5
 873       * @return external_single_structure
 874       */
 875      public static function delete_purpose_returns() {
 876          return new external_single_structure([
 877              'result' => new external_value(PARAM_BOOL, 'The processing result'),
 878              'warnings' => new external_warnings()
 879          ]);
 880      }
 881  
 882      /**
 883       * Parameter description for create_category_form().
 884       *
 885       * @since Moodle 3.5
 886       * @return external_function_parameters
 887       */
 888      public static function create_category_form_parameters() {
 889          return new external_function_parameters([
 890              'jsonformdata' => new external_value(PARAM_RAW, 'The data to create the category, encoded as a json array')
 891          ]);
 892      }
 893  
 894      /**
 895       * Creates a data category from form data.
 896       *
 897       * @since Moodle 3.5
 898       * @param string $jsonformdata
 899       * @return array
 900       */
 901      public static function create_category_form($jsonformdata) {
 902          global $PAGE;
 903  
 904          $warnings = [];
 905  
 906          $params = external_api::validate_parameters(self::create_category_form_parameters(), [
 907              'jsonformdata' => $jsonformdata
 908          ]);
 909  
 910          // Validate context and access to manage the registry.
 911          self::validate_context(\context_system::instance());
 912          api::check_can_manage_data_registry();
 913  
 914          $serialiseddata = json_decode($params['jsonformdata']);
 915          $data = array();
 916          parse_str($serialiseddata, $data);
 917  
 918          $category = new \tool_dataprivacy\category(0);
 919          $mform = new \tool_dataprivacy\form\category(null, ['persistent' => $category], 'post', '', null, true, $data);
 920  
 921          $validationerrors = true;
 922          if ($validateddata = $mform->get_data()) {
 923              $category = api::create_category($validateddata);
 924              $validationerrors = false;
 925          } else if ($errors = $mform->is_validated()) {
 926              throw new moodle_exception('generalerror');
 927          }
 928  
 929          $exporter = new category_exporter($category, ['context' => \context_system::instance()]);
 930          return [
 931              'category' => $exporter->export($PAGE->get_renderer('core')),
 932              'validationerrors' => $validationerrors,
 933              'warnings' => $warnings
 934          ];
 935      }
 936  
 937      /**
 938       * Returns for create_category_form().
 939       *
 940       * @since Moodle 3.5
 941       * @return external_single_structure
 942       */
 943      public static function create_category_form_returns() {
 944          return new external_single_structure([
 945              'category' => category_exporter::get_read_structure(),
 946              'validationerrors' => new external_value(PARAM_BOOL, 'Were there validation errors', VALUE_REQUIRED),
 947              'warnings' => new external_warnings()
 948          ]);
 949      }
 950  
 951      /**
 952       * Parameter description for delete_category().
 953       *
 954       * @since Moodle 3.5
 955       * @return external_function_parameters
 956       */
 957      public static function delete_category_parameters() {
 958          return new external_function_parameters([
 959              'id' => new external_value(PARAM_INT, 'The category ID', VALUE_REQUIRED)
 960          ]);
 961      }
 962  
 963      /**
 964       * Deletes a data category.
 965       *
 966       * @since Moodle 3.5
 967       * @param int $id The ID.
 968       * @return array
 969       * @throws invalid_persistent_exception
 970       * @throws coding_exception
 971       * @throws invalid_parameter_exception
 972       */
 973      public static function delete_category($id) {
 974          global $USER;
 975  
 976          $params = external_api::validate_parameters(self::delete_category_parameters(), [
 977              'id' => $id
 978          ]);
 979  
 980          // Validate context and access to manage the registry.
 981          self::validate_context(\context_system::instance());
 982          api::check_can_manage_data_registry();
 983  
 984          $result = api::delete_category($params['id']);
 985  
 986          return [
 987              'result' => $result,
 988              'warnings' => []
 989          ];
 990      }
 991  
 992      /**
 993       * Parameter description for delete_category().
 994       *
 995       * @since Moodle 3.5
 996       * @return external_single_structure
 997       */
 998      public static function delete_category_returns() {
 999          return new external_single_structure([
1000              'result' => new external_value(PARAM_BOOL, 'The processing result'),
1001              'warnings' => new external_warnings()
1002          ]);
1003      }
1004  
1005      /**
1006       * Parameter description for set_contextlevel_form().
1007       *
1008       * @since Moodle 3.5
1009       * @return external_function_parameters
1010       */
1011      public static function set_contextlevel_form_parameters() {
1012          return new external_function_parameters([
1013              'jsonformdata' => new external_value(PARAM_RAW, 'The context level data, encoded as a json array')
1014          ]);
1015      }
1016  
1017      /**
1018       * Creates a data category from form data.
1019       *
1020       * @since Moodle 3.5
1021       * @param string $jsonformdata
1022       * @return array
1023       */
1024      public static function set_contextlevel_form($jsonformdata) {
1025          global $PAGE;
1026  
1027          $warnings = [];
1028  
1029          $params = external_api::validate_parameters(self::set_contextlevel_form_parameters(), [
1030              'jsonformdata' => $jsonformdata
1031          ]);
1032  
1033          // Validate context and access to manage the registry.
1034          self::validate_context(\context_system::instance());
1035          api::check_can_manage_data_registry();
1036  
1037          $serialiseddata = json_decode($params['jsonformdata']);
1038          $data = array();
1039          parse_str($serialiseddata, $data);
1040  
1041          $contextlevel = $data['contextlevel'];
1042  
1043          $customdata = \tool_dataprivacy\form\contextlevel::get_contextlevel_customdata($contextlevel);
1044          $mform = new \tool_dataprivacy\form\contextlevel(null, $customdata, 'post', '', null, true, $data);
1045          if ($validateddata = $mform->get_data()) {
1046              $contextlevel = api::set_contextlevel($validateddata);
1047          } else if ($errors = $mform->is_validated()) {
1048              $warnings[] = json_encode($errors);
1049          }
1050  
1051          if ($contextlevel) {
1052              $result = true;
1053          } else {
1054              $result = false;
1055          }
1056          return [
1057              'result' => $result,
1058              'warnings' => $warnings
1059          ];
1060      }
1061  
1062      /**
1063       * Returns for set_contextlevel_form().
1064       *
1065       * @since Moodle 3.5
1066       * @return external_single_structure
1067       */
1068      public static function set_contextlevel_form_returns() {
1069          return new external_single_structure([
1070              'result' => new external_value(PARAM_BOOL, 'Whether the data was properly set or not'),
1071              'warnings' => new external_warnings()
1072          ]);
1073      }
1074  
1075      /**
1076       * Parameter description for set_context_form().
1077       *
1078       * @since Moodle 3.5
1079       * @return external_function_parameters
1080       */
1081      public static function set_context_form_parameters() {
1082          return new external_function_parameters([
1083              'jsonformdata' => new external_value(PARAM_RAW, 'The context level data, encoded as a json array')
1084          ]);
1085      }
1086  
1087      /**
1088       * Creates a data category from form data.
1089       *
1090       * @since Moodle 3.5
1091       * @param string $jsonformdata
1092       * @return array
1093       */
1094      public static function set_context_form($jsonformdata) {
1095          global $PAGE;
1096  
1097          $warnings = [];
1098  
1099          $params = external_api::validate_parameters(self::set_context_form_parameters(), [
1100              'jsonformdata' => $jsonformdata
1101          ]);
1102  
1103          // Validate context and access to manage the registry.
1104          self::validate_context(\context_system::instance());
1105          api::check_can_manage_data_registry();
1106  
1107          $serialiseddata = json_decode($params['jsonformdata']);
1108          $data = array();
1109          parse_str($serialiseddata, $data);
1110  
1111          $context = context_helper::instance_by_id($data['contextid']);
1112          $customdata = \tool_dataprivacy\form\context_instance::get_context_instance_customdata($context);
1113          $mform = new \tool_dataprivacy\form\context_instance(null, $customdata, 'post', '', null, true, $data);
1114          if ($validateddata = $mform->get_data()) {
1115              api::check_can_manage_data_registry($validateddata->contextid);
1116              $context = api::set_context_instance($validateddata);
1117          } else if ($errors = $mform->is_validated()) {
1118              $warnings[] = json_encode($errors);
1119              throw new moodle_exception('generalerror');
1120          }
1121  
1122          if ($context) {
1123              $result = true;
1124          } else {
1125              $result = false;
1126          }
1127          return [
1128              'result' => $result,
1129              'warnings' => $warnings
1130          ];
1131      }
1132  
1133      /**
1134       * Returns for set_context_form().
1135       *
1136       * @since Moodle 3.5
1137       * @return external_single_structure
1138       */
1139      public static function set_context_form_returns() {
1140          return new external_single_structure([
1141              'result' => new external_value(PARAM_BOOL, 'Whether the data was properly set or not'),
1142              'warnings' => new external_warnings()
1143          ]);
1144      }
1145  
1146      /**
1147       * Parameter description for tree_extra_branches().
1148       *
1149       * @since Moodle 3.5
1150       * @return external_function_parameters
1151       */
1152      public static function tree_extra_branches_parameters() {
1153          return new external_function_parameters([
1154              'contextid' => new external_value(PARAM_INT, 'The context id to expand'),
1155              'element' => new external_value(PARAM_ALPHA, 'The element we are interested on')
1156          ]);
1157      }
1158  
1159      /**
1160       * Returns tree extra branches.
1161       *
1162       * @since Moodle 3.5
1163       * @param int $contextid
1164       * @param string $element
1165       * @return array
1166       */
1167      public static function tree_extra_branches($contextid, $element) {
1168  
1169          $params = external_api::validate_parameters(self::tree_extra_branches_parameters(), [
1170              'contextid' => $contextid,
1171              'element' => $element,
1172          ]);
1173  
1174          $context = context_helper::instance_by_id($params['contextid']);
1175  
1176          self::validate_context($context);
1177          api::check_can_manage_data_registry($context->id);
1178  
1179          switch ($params['element']) {
1180              case 'course':
1181                  $branches = data_registry_page::get_courses_branch($context);
1182                  break;
1183              case 'module':
1184                  $branches = data_registry_page::get_modules_branch($context);
1185                  break;
1186              case 'block':
1187                  $branches = data_registry_page::get_blocks_branch($context);
1188                  break;
1189              default:
1190                  throw new \moodle_exception('Unsupported element provided.');
1191          }
1192  
1193          return [
1194              'branches' => $branches,
1195              'warnings' => [],
1196          ];
1197      }
1198  
1199      /**
1200       * Returns for tree_extra_branches().
1201       *
1202       * @since Moodle 3.5
1203       * @return external_single_structure
1204       */
1205      public static function tree_extra_branches_returns() {
1206          return new external_single_structure([
1207              'branches' => new external_multiple_structure(self::get_tree_node_structure(true)),
1208              'warnings' => new external_warnings()
1209          ]);
1210      }
1211  
1212      /**
1213       * Parameters for confirm_contexts_for_deletion().
1214       *
1215       * @since Moodle 3.5
1216       * @return external_function_parameters
1217       */
1218      public static function confirm_contexts_for_deletion_parameters() {
1219          return new external_function_parameters([
1220              'ids' => new external_multiple_structure(
1221                  new external_value(PARAM_INT, 'Expired context record ID', VALUE_REQUIRED),
1222                  'Array of expired context record IDs', VALUE_DEFAULT, []
1223              ),
1224          ]);
1225      }
1226  
1227      /**
1228       * Confirm a given array of expired context record IDs
1229       *
1230       * @since Moodle 3.5
1231       * @param int[] $ids Array of record IDs from the expired contexts table.
1232       * @return array
1233       * @throws coding_exception
1234       * @throws dml_exception
1235       * @throws invalid_parameter_exception
1236       * @throws restricted_context_exception
1237       */
1238      public static function confirm_contexts_for_deletion($ids) {
1239          $warnings = [];
1240          $params = external_api::validate_parameters(self::confirm_contexts_for_deletion_parameters(), [
1241              'ids' => $ids
1242          ]);
1243          $ids = $params['ids'];
1244  
1245          // Validate context and access to manage the registry.
1246          self::validate_context(\context_system::instance());
1247          api::check_can_manage_data_registry();
1248  
1249          $result = true;
1250          if (!empty($ids)) {
1251              $expiredcontextstoapprove = [];
1252              // Loop through the deletion of expired contexts and their children if necessary.
1253              foreach ($ids as $id) {
1254                  $expiredcontext = new expired_context($id);
1255                  $targetcontext = context_helper::instance_by_id($expiredcontext->get('contextid'));
1256  
1257                  if (!$targetcontext instanceof \context_user) {
1258                      // Fetch this context's child contexts. Make sure that all of the child contexts are flagged for deletion.
1259                      // User context children do not need to be considered.
1260                      $childcontexts = $targetcontext->get_child_contexts();
1261                      foreach ($childcontexts as $child) {
1262                          if ($expiredchildcontext = expired_context::get_record(['contextid' => $child->id])) {
1263                              // Add this child context to the list for approval.
1264                              $expiredcontextstoapprove[] = $expiredchildcontext;
1265                          } else {
1266                              // This context has not yet been flagged for deletion.
1267                              $result = false;
1268                              $message = get_string('errorcontexthasunexpiredchildren', 'tool_dataprivacy',
1269                                  $targetcontext->get_context_name(false));
1270                              $warnings[] = [
1271                                  'item' => 'tool_dataprivacy_ctxexpired',
1272                                  'warningcode' => 'errorcontexthasunexpiredchildren',
1273                                  'message' => $message
1274                              ];
1275                              // Exit the process.
1276                              break 2;
1277                          }
1278                      }
1279                  }
1280  
1281                  $expiredcontextstoapprove[] = $expiredcontext;
1282              }
1283  
1284              // Proceed with the approval if everything's in order.
1285              if ($result) {
1286                  // Mark expired contexts as approved for deletion.
1287                  foreach ($expiredcontextstoapprove as $expired) {
1288                      // Only mark expired contexts that are pending approval.
1289                      if ($expired->get('status') == expired_context::STATUS_EXPIRED) {
1290                          api::set_expired_context_status($expired, expired_context::STATUS_APPROVED);
1291                      }
1292                  }
1293              }
1294  
1295          } else {
1296              // We don't have anything to process.
1297              $result = false;
1298              $warnings[] = [
1299                  'item' => 'tool_dataprivacy_ctxexpired',
1300                  'warningcode' => 'errornoexpiredcontexts',
1301                  'message' => get_string('errornoexpiredcontexts', 'tool_dataprivacy')
1302              ];
1303          }
1304  
1305          return [
1306              'result' => $result,
1307              'warnings' => $warnings
1308          ];
1309      }
1310  
1311      /**
1312       * Returns for confirm_contexts_for_deletion().
1313       *
1314       * @since Moodle 3.5
1315       * @return external_single_structure
1316       */
1317      public static function confirm_contexts_for_deletion_returns() {
1318          return new external_single_structure([
1319              'result' => new external_value(PARAM_BOOL, 'Whether the record was properly marked for deletion or not'),
1320              'warnings' => new external_warnings()
1321          ]);
1322      }
1323  
1324      /**
1325       * Parameters for set_context_defaults().
1326       *
1327       * @return external_function_parameters
1328       */
1329      public static function set_context_defaults_parameters() {
1330          return new external_function_parameters([
1331              'contextlevel' => new external_value(PARAM_INT, 'The context level', VALUE_REQUIRED),
1332              'category' => new external_value(PARAM_INT, 'The default category for the given context level', VALUE_REQUIRED),
1333              'purpose' => new external_value(PARAM_INT, 'The default purpose for the given context level', VALUE_REQUIRED),
1334              'activity' => new external_value(PARAM_PLUGIN, 'The plugin name of the activity', VALUE_DEFAULT, null),
1335              'override' => new external_value(PARAM_BOOL, 'Whether to override existing instances with the defaults', VALUE_DEFAULT,
1336                  false),
1337          ]);
1338      }
1339  
1340      /**
1341       * Updates the default category and purpose for a given context level (and optionally, a plugin).
1342       *
1343       * @param int $contextlevel The context level.
1344       * @param int $category The ID matching the category.
1345       * @param int $purpose The ID matching the purpose record.
1346       * @param int $activity The name of the activity that we're making a defaults configuration for.
1347       * @param bool $override Whether to override the purpose/categories of existing instances to these defaults.
1348       * @return array
1349       */
1350      public static function set_context_defaults($contextlevel, $category, $purpose, $activity, $override) {
1351          $warnings = [];
1352  
1353          $params = external_api::validate_parameters(self::set_context_defaults_parameters(), [
1354              'contextlevel' => $contextlevel,
1355              'category' => $category,
1356              'purpose' => $purpose,
1357              'activity' => $activity,
1358              'override' => $override,
1359          ]);
1360          $contextlevel = $params['contextlevel'];
1361          $category = $params['category'];
1362          $purpose = $params['purpose'];
1363          $activity = $params['activity'];
1364          $override = $params['override'];
1365  
1366          // Validate context.
1367          $context = context_system::instance();
1368          self::validate_context($context);
1369          api::check_can_manage_data_registry();
1370  
1371          // Set the context defaults.
1372          $result = api::set_context_defaults($contextlevel, $category, $purpose, $activity, $override);
1373  
1374          return [
1375              'result' => $result,
1376              'warnings' => $warnings
1377          ];
1378      }
1379  
1380      /**
1381       * Returns for set_context_defaults().
1382       *
1383       * @return external_single_structure
1384       */
1385      public static function set_context_defaults_returns() {
1386          return new external_single_structure([
1387              'result' => new external_value(PARAM_BOOL, 'Whether the context defaults were successfully set or not'),
1388              'warnings' => new external_warnings()
1389          ]);
1390      }
1391  
1392      /**
1393       * Parameters for get_category_options().
1394       *
1395       * @return external_function_parameters
1396       */
1397      public static function get_category_options_parameters() {
1398          return new external_function_parameters([
1399              'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
1400              'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
1401          ]);
1402      }
1403  
1404      /**
1405       * Fetches a list of data category options containing category IDs as keys and the category name for the value.
1406       *
1407       * @param bool $includeinherit Whether to include the "Inherit" option.
1408       * @param bool $includenotset Whether to include the "Not set" option.
1409       * @return array
1410       */
1411      public static function get_category_options($includeinherit, $includenotset) {
1412          $warnings = [];
1413  
1414          $params = self::validate_parameters(self::get_category_options_parameters(), [
1415              'includeinherit' => $includeinherit,
1416              'includenotset' => $includenotset
1417          ]);
1418          $includeinherit = $params['includeinherit'];
1419          $includenotset = $params['includenotset'];
1420  
1421          $context = context_system::instance();
1422          self::validate_context($context);
1423          api::check_can_manage_data_registry();
1424  
1425          $categories = api::get_categories();
1426          $options = data_registry_page::category_options($categories, $includenotset, $includeinherit);
1427          $categoryoptions = [];
1428          foreach ($options as $id => $name) {
1429              $categoryoptions[] = [
1430                  'id' => $id,
1431                  'name' => $name,
1432              ];
1433          }
1434  
1435          return [
1436              'options' => $categoryoptions,
1437              'warnings' => $warnings
1438          ];
1439      }
1440  
1441      /**
1442       * Returns for get_category_options().
1443       *
1444       * @return external_single_structure
1445       */
1446      public static function get_category_options_returns() {
1447          $optiondefinition = new external_single_structure(
1448              [
1449                  'id' => new external_value(PARAM_INT, 'The category ID'),
1450                  'name' => new external_value(PARAM_TEXT, 'The category name'),
1451              ]
1452          );
1453  
1454          return new external_single_structure([
1455              'options' => new external_multiple_structure($optiondefinition),
1456              'warnings' => new external_warnings()
1457          ]);
1458      }
1459  
1460      /**
1461       * Parameters for get_purpose_options().
1462       *
1463       * @return external_function_parameters
1464       */
1465      public static function get_purpose_options_parameters() {
1466          return new external_function_parameters([
1467              'includeinherit' => new external_value(PARAM_BOOL, 'Include option "Inherit"', VALUE_DEFAULT, true),
1468              'includenotset' => new external_value(PARAM_BOOL, 'Include option "Not set"', VALUE_DEFAULT, false),
1469          ]);
1470      }
1471  
1472      /**
1473       * Fetches a list of data storage purposes containing purpose IDs as keys and the purpose name for the value.
1474       *
1475       * @param bool $includeinherit Whether to include the "Inherit" option.
1476       * @param bool $includenotset Whether to include the "Not set" option.
1477       * @return array
1478       */
1479      public static function get_purpose_options($includeinherit, $includenotset) {
1480          $warnings = [];
1481  
1482          $params = self::validate_parameters(self::get_category_options_parameters(), [
1483              'includeinherit' => $includeinherit,
1484              'includenotset' => $includenotset
1485          ]);
1486          $includeinherit = $params['includeinherit'];
1487          $includenotset = $params['includenotset'];
1488  
1489          $context = context_system::instance();
1490          self::validate_context($context);
1491  
1492          $purposes = api::get_purposes();
1493          $options = data_registry_page::purpose_options($purposes, $includenotset, $includeinherit);
1494          $purposeoptions = [];
1495          foreach ($options as $id => $name) {
1496              $purposeoptions[] = [
1497                  'id' => $id,
1498                  'name' => $name,
1499              ];
1500          }
1501  
1502          return [
1503              'options' => $purposeoptions,
1504              'warnings' => $warnings
1505          ];
1506      }
1507  
1508      /**
1509       * Returns for get_purpose_options().
1510       *
1511       * @return external_single_structure
1512       */
1513      public static function get_purpose_options_returns() {
1514          $optiondefinition = new external_single_structure(
1515              [
1516                  'id' => new external_value(PARAM_INT, 'The purpose ID'),
1517                  'name' => new external_value(PARAM_TEXT, 'The purpose name'),
1518              ]
1519          );
1520  
1521          return new external_single_structure([
1522              'options' => new external_multiple_structure($optiondefinition),
1523              'warnings' => new external_warnings()
1524          ]);
1525      }
1526  
1527      /**
1528       * Parameters for get_activity_options().
1529       *
1530       * @return external_function_parameters
1531       */
1532      public static function get_activity_options_parameters() {
1533          return new external_function_parameters([
1534              'nodefaults' => new external_value(PARAM_BOOL, 'Whether to fetch all activities or only those without defaults',
1535                  VALUE_DEFAULT, false),
1536          ]);
1537      }
1538  
1539      /**
1540       * Fetches a list of activity options for setting data registry defaults.
1541       *
1542       * @param boolean $nodefaults If false, it will fetch all of the activities. Otherwise, it will only fetch the activities
1543       *                            that don't have defaults yet (e.g. when adding a new activity module defaults).
1544       * @return array
1545       */
1546      public static function get_activity_options($nodefaults) {
1547          $warnings = [];
1548  
1549          $params = self::validate_parameters(self::get_activity_options_parameters(), [
1550              'nodefaults' => $nodefaults,
1551          ]);
1552          $nodefaults = $params['nodefaults'];
1553  
1554          $context = context_system::instance();
1555          self::validate_context($context);
1556  
1557          // Get activity module plugin info.
1558          $pluginmanager = \core_plugin_manager::instance();
1559          $modplugins = $pluginmanager->get_enabled_plugins('mod');
1560          $modoptions = [];
1561  
1562          // Get the module-level defaults. data_registry::get_defaults falls back to this when there are no activity defaults.
1563          list($levelpurpose, $levelcategory) = data_registry::get_defaults(CONTEXT_MODULE);
1564          foreach ($modplugins as $name) {
1565              // Check if we have default purpose and category for this module if we want don't want to fetch everything.
1566              if ($nodefaults) {
1567                  list($purpose, $category) = data_registry::get_defaults(CONTEXT_MODULE, $name);
1568                  // Compare this with the module-level defaults.
1569                  if ($purpose !== $levelpurpose || $category !== $levelcategory) {
1570                      // If the defaults for this activity has been already set, there's no need to add this in the list of options.
1571                      continue;
1572                  }
1573              }
1574  
1575              $displayname = $pluginmanager->plugin_name('mod_' . $name);
1576              $modoptions[] = (object)[
1577                  'name' => $name,
1578                  'displayname' => $displayname
1579              ];
1580          }
1581  
1582          return [
1583              'options' => $modoptions,
1584              'warnings' => $warnings
1585          ];
1586      }
1587  
1588      /**
1589       * Returns for get_category_options().
1590       *
1591       * @return external_single_structure
1592       */
1593      public static function get_activity_options_returns() {
1594          $optionsdefinition = new external_single_structure(
1595              [
1596                  'name' => new external_value(PARAM_TEXT, 'The plugin name of the activity'),
1597                  'displayname' => new external_value(PARAM_TEXT, 'The display name of the activity'),
1598              ]
1599          );
1600  
1601          return new external_single_structure([
1602              'options' => new external_multiple_structure($optionsdefinition),
1603              'warnings' => new external_warnings()
1604          ]);
1605      }
1606  
1607      /**
1608       * Gets the structure of a tree node (link + child branches).
1609       *
1610       * @since Moodle 3.5
1611       * @param bool $allowchildbranches
1612       * @return array
1613       */
1614      private static function get_tree_node_structure($allowchildbranches = true) {
1615          $fields = [
1616              'text' => new external_value(PARAM_RAW, 'The node text', VALUE_REQUIRED),
1617              'expandcontextid' => new external_value(PARAM_INT, 'The contextid this node expands', VALUE_REQUIRED),
1618              'expandelement' => new external_value(PARAM_ALPHA, 'What element is this node expanded to', VALUE_REQUIRED),
1619              'contextid' => new external_value(PARAM_INT, 'The node contextid', VALUE_REQUIRED),
1620              'contextlevel' => new external_value(PARAM_INT, 'The node contextlevel', VALUE_REQUIRED),
1621              'expanded' => new external_value(PARAM_INT, 'Is it expanded', VALUE_REQUIRED),
1622          ];
1623  
1624          if ($allowchildbranches) {
1625              // Passing false as we will not have more than 1 sub-level.
1626              $fields['branches'] = new external_multiple_structure(
1627                  self::get_tree_node_structure(false),
1628                  'Children node structure',
1629                  VALUE_OPTIONAL
1630              );
1631          } else {
1632              // We will only have 1 sub-level and we don't want an infinite get_tree_node_structure, this is a hacky
1633              // way to prevent this infinite loop when calling get_tree_node_structure recursively.
1634              $fields['branches'] = new external_multiple_structure(
1635                  new external_value(
1636                      PARAM_TEXT,
1637                      'Nothing really, it will always be an empty array',
1638                      VALUE_OPTIONAL
1639                  )
1640              );
1641          }
1642  
1643          return new external_single_structure($fields, 'Node structure', VALUE_OPTIONAL);
1644      }
1645  }