Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]

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