Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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