Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       1  <?php
       2  // This file is part of Moodle - http://moodle.org/
       3  //
       4  // Moodle is free software: you can redistribute it and/or modify
       5  // it under the terms of the GNU General Public License as published by
       6  // the Free Software Foundation, either version 3 of the License, or
       7  // (at your option) any later version.
       8  //
       9  // Moodle is distributed in the hope that it will be useful,
      10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12  // GNU General Public License for more details.
      13  //
      14  // You should have received a copy of the GNU General Public License
      15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
      16  /**
      17   * Class containing the external API functions functions for the Data Privacy tool.
      18   *
      19   * @package    tool_dataprivacy
      20   * @copyright  2018 Jun Pataleta
      21   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      22   */
      23  namespace tool_dataprivacy;
      24  defined('MOODLE_INTERNAL') || die();
      25  
      26  require_once("$CFG->libdir/externallib.php");
      27  require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
      28  
      29  use coding_exception;
      30  use context_helper;
      31  use context_system;
      32  use context_user;
      33  use core\invalid_persistent_exception;
      34  use core\notification;
      35  use core_user;
      36  use dml_exception;
      37  use external_api;
      38  use external_description;
      39  use external_function_parameters;
      40  use external_multiple_structure;
      41  use external_single_structure;
      42  use external_value;
      43  use external_warnings;
      44  use invalid_parameter_exception;
      45  use moodle_exception;
      46  use required_capability_exception;
      47  use restricted_context_exception;
      48  use tool_dataprivacy\external\category_exporter;
      49  use tool_dataprivacy\external\data_request_exporter;
      50  use tool_dataprivacy\external\purpose_exporter;
      51  use tool_dataprivacy\output\data_registry_page;
      52  
      53  /**
      54   * Class external.
      55   *
      56   * The external API for the Data Privacy tool.
      57   *
      58   * @copyright  2017 Jun Pataleta
      59   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      60   */
      61  class external extends external_api {
      62  
      63      /**
      64       * Parameter description for cancel_data_request().
      65       *
      66       * @since Moodle 3.5
      67       * @return external_function_parameters
      68       */
      69      public static function cancel_data_request_parameters() {
      70          return new external_function_parameters([
      71              'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
      72          ]);
      73      }
      74  
      75      /**
      76       * Cancel a data request.
      77       *
      78       * @since Moodle 3.5
      79       * @param int $requestid The request ID.
      80       * @return array
      81       * @throws invalid_persistent_exception
      82       * @throws coding_exception
      83       * @throws invalid_parameter_exception
      84       * @throws restricted_context_exception
      85       */
      86      public static function cancel_data_request($requestid) {
      87          global $USER;
      88  
      89          $warnings = [];
      90          $params = external_api::validate_parameters(self::cancel_data_request_parameters(), [
      91              'requestid' => $requestid
      92          ]);
      93          $requestid = $params['requestid'];
      94  
      95          // Validate context and access to manage the registry.
      96          $context = context_user::instance($USER->id);
      97          self::validate_context($context);
      98  
      99          // Ensure the request exists.
     100          $select = 'id = :id AND (userid = :userid OR requestedby = :requestedby)';
     101          $params = ['id' => $requestid, 'userid' => $USER->id, 'requestedby' => $USER->id];
     102          $requests = data_request::get_records_select($select, $params);
     103          $requestexists = count($requests) === 1;
     104  
     105          $result = false;
     106          if ($requestexists) {
     107              $request = reset($requests);
     108              $datasubject = $request->get('userid');
     109  
     110              if ($datasubject !== $USER->id) {
     111                  // The user is not the subject. Check that they can cancel this request.
     112                  if (!api::can_create_data_request_for_user($datasubject)) {
     113                      $forusercontext = \context_user::instance($datasubject);
     114                      throw new required_capability_exception($forusercontext,
     115                              'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
     116                  }
     117              }
     118  
     119              // TODO: Do we want a request to be non-cancellable past a certain point? E.g. When it's already approved/processing.
     120              $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_CANCELLED);
     121          } else {
     122              $warnings[] = [
     123                  'item' => $requestid,
     124                  'warningcode' => 'errorrequestnotfound',
     125                  'message' => get_string('errorrequestnotfound', 'tool_dataprivacy')
     126              ];
     127          }
     128  
     129          return [
     130              'result' => $result,
     131              'warnings' => $warnings
     132          ];
     133      }
     134  
     135      /**
     136       * Parameter description for cancel_data_request().
     137       *
     138       * @since Moodle 3.5
     139       * @return external_description
     140       */
     141      public static function cancel_data_request_returns() {
     142          return new external_single_structure([
     143              'result' => new external_value(PARAM_BOOL, 'The processing result'),
     144              'warnings' => new external_warnings()
     145          ]);
     146      }
     147  
     148      /**
     149       * Parameter description for contact_dpo().
     150       *
     151       * @since Moodle 3.5
     152       * @return external_function_parameters
     153       */
     154      public static function contact_dpo_parameters() {
     155          return new external_function_parameters([
     156              'message' => new external_value(PARAM_TEXT, 'The user\'s message to the Data Protection Officer(s)', VALUE_REQUIRED)
     157          ]);
     158      }
     159  
     160      /**
     161       * Make a general enquiry to a DPO.
     162       *
     163       * @since Moodle 3.5
     164       * @param string $message The message to be sent to the DPO.
     165       * @return array
     166       * @throws coding_exception
     167       * @throws invalid_parameter_exception
     168       * @throws invalid_persistent_exception
     169       * @throws restricted_context_exception
     170       * @throws dml_exception
     171       * @throws moodle_exception
     172       */
     173      public static function contact_dpo($message) {
     174          global $USER;
     175  
     176          $warnings = [];
     177          $params = external_api::validate_parameters(self::contact_dpo_parameters(), [
     178              'message' => $message
     179          ]);
     180          $message = $params['message'];
     181  
     182          // Validate context.
     183          $userid = $USER->id;
     184          $context = context_user::instance($userid);
     185          self::validate_context($context);
     186  
     187          // Lodge the request.
     188          $datarequest = new data_request();
     189          // The user the request is being made for.
     190          $datarequest->set('userid', $userid);
     191          // The user making the request.
     192          $datarequest->set('requestedby', $userid);
     193          // Set status.
     194          $datarequest->set('status', api::DATAREQUEST_STATUS_PENDING);
     195          // Set request type.
     196          $datarequest->set('type', api::DATAREQUEST_TYPE_OTHERS);
     197          // Set request comments.
     198          $datarequest->set('comments', $message);
     199  
     200          // Store subject access request.
     201          $datarequest->create();
     202  
     203          // Get the list of the site Data Protection Officers.
     204          $dpos = api::get_site_dpos();
     205  
     206          // Email the data request to the Data Protection Officer(s)/Admin(s).
     207          $result = true;
     208          foreach ($dpos as $dpo) {
     209              $sendresult = api::notify_dpo($dpo, $datarequest);
     210              if (!$sendresult) {
     211                  $result = false;
     212                  $warnings[] = [
     213                      'item' => $dpo->id,
     214                      'warningcode' => 'errorsendingtodpo',
     215                      'message' => get_string('errorsendingmessagetodpo', 'tool_dataprivacy',
     216                          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  }