Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Privacy Subsystem implementation for core_role.
  19   *
  20   * @package    core_role
  21   * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_role\privacy;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use \core_privacy\local\metadata\collection;
  30  use \core_privacy\local\request\contextlist;
  31  use \core_privacy\local\request\approved_contextlist;
  32  use \core_privacy\local\request\transform;
  33  use \core_privacy\local\request\writer;
  34  use \core_privacy\local\request\userlist;
  35  use \core_privacy\local\request\approved_userlist;
  36  
  37  /**
  38   * Privacy provider for core_role.
  39   *
  40   * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
  41   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class provider implements
  44      \core_privacy\local\metadata\provider,
  45      \core_privacy\local\request\subsystem\provider,
  46      \core_privacy\local\request\subsystem\plugin_provider,
  47      \core_privacy\local\request\user_preference_provider,
  48      \core_privacy\local\request\core_userlist_provider {
  49  
  50      /**
  51       * Get information about the user data stored by this plugin.
  52       *
  53       * @param  collection $collection An object for storing metadata.
  54       * @return collection The metadata.
  55       */
  56      public static function get_metadata(collection $collection) : collection {
  57          $rolecapabilities = [
  58              'roleid' => 'privacy:metadata:role_capabilities:roleid',
  59              'capability' => 'privacy:metadata:role_capabilities:capability',
  60              'permission' => 'privacy:metadata:role_capabilities:permission',
  61              'timemodified' => 'privacy:metadata:role_capabilities:timemodified',
  62              'modifierid' => 'privacy:metadata:role_capabilities:modifierid'
  63          ];
  64          $roleassignments = [
  65              'roleid' => 'privacy:metadata:role_assignments:roleid',
  66              'userid' => 'privacy:metadata:role_assignments:userid',
  67              'timemodified' => 'privacy:metadata:role_assignments:timemodified',
  68              'modifierid' => 'privacy:metadata:role_assignments:modifierid',
  69              'component' => 'privacy:metadata:role_assignments:component',
  70              'itemid' => 'privacy:metadata:role_assignments:itemid'
  71          ];
  72          $collection->add_database_table('role_capabilities', $rolecapabilities,
  73              'privacy:metadata:role_capabilities:tableexplanation');
  74          $collection->add_database_table('role_assignments', $roleassignments,
  75              'privacy:metadata:role_assignments:tableexplanation');
  76  
  77          $collection->add_user_preference('definerole_showadvanced',
  78              'privacy:metadata:preference:showadvanced');
  79  
  80          return $collection;
  81      }
  82      /**
  83       * Export all user preferences for the plugin.
  84       *
  85       * @param   int         $userid The userid of the user whose data is to be exported.
  86       */
  87      public static function export_user_preferences(int $userid) {
  88          $showadvanced = get_user_preferences('definerole_showadvanced', null, $userid);
  89          if ($showadvanced !== null) {
  90              writer::export_user_preference('core_role',
  91                  'definerole_showadvanced',
  92                  transform::yesno($showadvanced),
  93                  get_string('privacy:metadata:preference:showadvanced', 'core_role')
  94              );
  95          }
  96      }
  97      /**
  98       * Return all contexts for this userid.
  99       *
 100       * @param  int $userid The user ID.
 101       * @return contextlist The list of context IDs.
 102       */
 103      public static function get_contexts_for_userid(int $userid) : contextlist {
 104          global $DB;
 105  
 106          $contextlist = new contextlist();
 107  
 108          // The role_capabilities table contains user data.
 109          $contexts = [
 110              CONTEXT_SYSTEM,
 111              CONTEXT_USER,
 112              CONTEXT_COURSECAT,
 113              CONTEXT_COURSE,
 114              CONTEXT_MODULE,
 115              CONTEXT_BLOCK
 116          ];
 117          list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
 118          $sql = "SELECT ctx.id
 119                    FROM {context} ctx
 120                    JOIN {role_capabilities} rc
 121                      ON rc.contextid = ctx.id
 122                     AND ((ctx.contextlevel {$insql} AND rc.modifierid = :modifierid)
 123                      OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))";
 124          $params = [
 125              'modifierid' => $userid,
 126              'contextlevel' => CONTEXT_USER,
 127              'userid' => $userid
 128           ];
 129          $params += $inparams;
 130  
 131          $contextlist->add_from_sql($sql, $params);
 132  
 133          // The role_assignments table contains user data.
 134          $contexts = [
 135              CONTEXT_SYSTEM,
 136              CONTEXT_USER,
 137              CONTEXT_COURSECAT,
 138              CONTEXT_COURSE,
 139              CONTEXT_MODULE,
 140              CONTEXT_BLOCK
 141          ];
 142          list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
 143          $params = [
 144              'userid' => $userid,
 145              'modifierid' => $userid
 146           ];
 147          $params += $inparams;
 148          $sql = "SELECT ctx.id
 149                    FROM {role_assignments} ra
 150                    JOIN {context} ctx
 151                      ON ctx.id = ra.contextid
 152                     AND ctx.contextlevel {$insql}
 153                   WHERE (ra.userid = :userid
 154                      OR ra.modifierid = :modifierid)
 155                     AND ra.component != 'tool_cohortroles'";
 156          $contextlist->add_from_sql($sql, $params);
 157  
 158          return $contextlist;
 159      }
 160  
 161      /**
 162       * Get the list of users within a specific context.
 163       *
 164       * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
 165       */
 166      public static function get_users_in_context(userlist $userlist) {
 167  
 168          if (empty($userlist)) {
 169              return;
 170          }
 171  
 172          $context = $userlist->get_context();
 173  
 174          // Include users who created or modified role capabilities.
 175          $sql = "SELECT modifierid as userid
 176                    FROM {role_capabilities}
 177                   WHERE contextid = :contextid";
 178  
 179          $params = [
 180              'contextid' => $context->id
 181          ];
 182  
 183          $userlist->add_from_sql('userid', $sql, $params);
 184  
 185          // Include users that have a role assigned to them.
 186          $sql = "SELECT userid
 187                    FROM {role_assignments}
 188                   WHERE contextid = :contextid";
 189  
 190          $userlist->add_from_sql('userid', $sql, $params);
 191  
 192          // Include users who created or modified the role assignment.
 193          // Differentiate and exclude special cases where tool_cohortroles adds records through the
 194          // "Assign user roles to cohort" feature into the role_assignments table.
 195          // These records should be separately processed in tool_cohortroles.
 196          $sql = "SELECT modifierid as userid
 197                    FROM {role_assignments}
 198                   WHERE contextid = :contextid
 199                         AND component != 'tool_cohortroles'";
 200  
 201          $userlist->add_from_sql('userid', $sql, $params);
 202      }
 203  
 204      /**
 205       * Export all user data for the specified user, in the specified contexts.
 206       *
 207       * @param  approved_contextlist $contextlist The list of approved contexts for a user.
 208       */
 209      public static function export_user_data(approved_contextlist $contextlist) {
 210          global $DB;
 211  
 212          if (empty($contextlist)) {
 213               return;
 214          }
 215  
 216          $rolesnames = self::get_roles_name();
 217          $userid = $contextlist->get_user()->id;
 218          $ctxfields = \context_helper::get_preload_record_columns_sql('ctx');
 219          list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
 220  
 221          // Role Assignments export data.
 222          $contexts = [
 223              CONTEXT_SYSTEM,
 224              CONTEXT_USER,
 225              CONTEXT_COURSECAT,
 226              CONTEXT_COURSE,
 227              CONTEXT_MODULE,
 228              CONTEXT_BLOCK
 229          ];
 230          list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
 231          $sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, $ctxfields
 232                    FROM {role_assignments} ra
 233                    JOIN {context} ctx
 234                      ON ctx.id = ra.contextid
 235                     AND ctx.contextlevel {$inctxsql}
 236                     AND (ra.userid = :userid OR ra.modifierid = :modifierid)
 237                     AND ra.component != 'tool_cohortroles'
 238                    JOIN {role} r
 239                      ON r.id = ra.roleid
 240                   WHERE ctx.id {$insql}";
 241          $params = ['userid' => $userid, 'modifierid' => $userid];
 242          $params += $inparams;
 243          $params += $ctxparams;
 244          $assignments = $DB->get_recordset_sql($sql, $params);
 245          foreach ($assignments as $assignment) {
 246              \context_helper::preload_from_record($assignment);
 247              $alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
 248                  'timemodified' => transform::datetime($assignment->timemodified),
 249                  'userid' => transform::user($assignment->userid),
 250                  'modifierid' => transform::user($assignment->modifierid)
 251              ];
 252          }
 253          $assignments->close();
 254          if (!empty($alldata)) {
 255              array_walk($alldata, function($roledata, $contextid) {
 256                  $context = \context::instance_by_id($contextid);
 257                  array_walk($roledata, function($data, $rolename) use ($context) {
 258                      writer::with_context($context)->export_data(
 259                              [get_string('privacy:metadata:role_assignments', 'core_role'), $rolename],
 260                              (object)$data);
 261                  });
 262              });
 263              unset($alldata);
 264          }
 265  
 266          // Role Capabilities export data.
 267          $strpermissions = self::get_permissions_name();
 268          $contexts = [
 269              CONTEXT_SYSTEM,
 270              CONTEXT_USER,
 271              CONTEXT_COURSECAT,
 272              CONTEXT_COURSE,
 273              CONTEXT_MODULE,
 274              CONTEXT_BLOCK
 275          ];
 276          list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
 277          $sql = "SELECT rc.id, rc.contextid, rc.capability, rc.permission, rc.timemodified, rc.roleid, $ctxfields
 278                    FROM {context} ctx
 279                    JOIN {role_capabilities} rc
 280                      ON rc.contextid = ctx.id
 281                     AND ((ctx.contextlevel {$inctxsql} AND rc.modifierid = :modifierid)
 282                      OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))
 283                   WHERE ctx.id {$insql}";
 284          $params = [
 285              'modifierid' => $userid,
 286              'contextlevel' => CONTEXT_USER,
 287              'userid' => $userid
 288           ];
 289          $params += $inparams;
 290          $params += $ctxparams;
 291          $capabilities = $DB->get_recordset_sql($sql, $params);
 292          foreach ($capabilities as $capability) {
 293              \context_helper::preload_from_record($capability);
 294              $alldata[$capability->contextid][$rolesnames[$capability->roleid]][] = (object)[
 295                  'timemodified' => transform::datetime($capability->timemodified),
 296                  'capability' => $capability->capability,
 297                  'permission' => $strpermissions[$capability->permission]
 298              ];
 299          }
 300          $capabilities->close();
 301          if (!empty($alldata)) {
 302              array_walk($alldata, function($capdata, $contextid) {
 303                  $context = \context::instance_by_id($contextid);
 304                  array_walk($capdata, function($data, $rolename) use ($context) {
 305                      writer::with_context($context)->export_data(
 306                              [get_string('privacy:metadata:role_capabilities', 'core_role'), $rolename],
 307                              (object)$data);
 308                  });
 309              });
 310          }
 311      }
 312      /**
 313       * Exports the data relating to tool_cohortroles component on role assignments by
 314       * Assign user roles to cohort feature.
 315       *
 316       * @param  int $userid The user ID.
 317       */
 318      public static function export_user_role_to_cohort(int $userid) {
 319          global $DB;
 320  
 321          $rolesnames = self::get_roles_name();
 322          $sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, r.id as roleid
 323                    FROM {role_assignments} ra
 324                    JOIN {context} ctx
 325                      ON ctx.id = ra.contextid
 326                     AND ctx.contextlevel = :contextlevel
 327                     AND ra.component = 'tool_cohortroles'
 328                    JOIN {role} r
 329                      ON r.id = ra.roleid
 330                   WHERE ctx.instanceid = :instanceid
 331                      OR ra.userid = :userid";
 332          $params = ['userid' => $userid, 'instanceid' => $userid, 'contextlevel' => CONTEXT_USER];
 333          $assignments = $DB->get_recordset_sql($sql, $params);
 334          foreach ($assignments as $assignment) {
 335              $alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
 336                  'timemodified' => transform::datetime($assignment->timemodified),
 337                  'userid' => transform::user($assignment->userid),
 338                  'modifierid' => transform::user($assignment->modifierid)
 339              ];
 340          }
 341          $assignments->close();
 342          if (!empty($alldata)) {
 343              array_walk($alldata, function($roledata, $contextid) {
 344                  $context = \context::instance_by_id($contextid);
 345                  array_walk($roledata, function($data, $rolename) use ($context) {
 346                      writer::with_context($context)->export_related_data(
 347                              [get_string('privacy:metadata:role_cohortroles', 'core_role'), $rolename], 'cohortroles',
 348                              (object)$data);
 349                  });
 350              });
 351          }
 352      }
 353      /**
 354       * Delete all user data for this context.
 355       *
 356       * @param  \context $context The context to delete data for.
 357       */
 358      public static function delete_data_for_all_users_in_context(\context $context) {
 359          global $DB;
 360  
 361          // Don't remove data from role_capabilities.
 362          // Because this data affects the whole Moodle, there are override capabilities.
 363          // Don't belong to the modifier user.
 364  
 365          // Remove data from role_assignments.
 366          $DB->delete_records('role_assignments', ['contextid' => $context->id]);
 367      }
 368  
 369      /**
 370       * Delete multiple users within a single context.
 371       *
 372       * @param approved_userlist $userlist The approved context and user information to delete information for.
 373       */
 374      public static function delete_data_for_users(approved_userlist $userlist) {
 375          global $DB;
 376  
 377          // Don't remove data from role_capabilities.
 378          // Because this data affects the whole Moodle, there are override capabilities.
 379          // Don't belong to the modifier user.
 380          $context = $userlist->get_context();
 381          $userids = $userlist->get_userids();
 382  
 383          if (empty($userids)) {
 384              return;
 385          }
 386          list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
 387          $params = ['contextid' => $context->id] + $userparams;
 388  
 389          // Remove data from role_assignments.
 390          $DB->delete_records_select('role_assignments',
 391              "contextid = :contextid AND userid {$usersql}", $params);
 392      }
 393  
 394      /**
 395       * Delete all user data for this user only.
 396       *
 397       * @param  approved_contextlist $contextlist The list of approved contexts for a user.
 398       */
 399      public static function delete_data_for_user(approved_contextlist $contextlist) {
 400          global $DB;
 401  
 402          // Don't remove data from role_capabilities.
 403          // Because this data affects the whole Moodle, there are override capabilities.
 404          // Don't belong to the modifier user.
 405  
 406          // Remove data from role_assignments.
 407          if (empty($contextlist->count())) {
 408              return;
 409          }
 410          $userid = $contextlist->get_user()->id;
 411          $contextids = $contextlist->get_contextids();
 412  
 413          list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
 414          $params = ['userid' => $userid] + $contextparams;
 415  
 416          // Only delete the roles assignments where the user is assigned in all contexts.
 417          $DB->delete_records_select('role_assignments',
 418              "userid = :userid AND contextid {$contextsql}", $params);
 419      }
 420      /**
 421       * Delete user entries in role_assignments related to the feature
 422       * Assign user roles to cohort feature.
 423       *
 424       * @param  int $userid The user ID.
 425       */
 426      public static function delete_user_role_to_cohort(int $userid) {
 427          global $DB;
 428  
 429          // Delete entries where userid is a mentor by tool_cohortroles.
 430          $DB->delete_records('role_assignments', ['userid' => $userid, 'component' => 'tool_cohortroles']);
 431      }
 432      /**
 433       * Get all the localised roles name in a simple array.
 434       *
 435       * @return array Array of name of the roles by roleid.
 436       */
 437      protected static function get_roles_name() {
 438          $roles = role_fix_names(get_all_roles(), \context_system::instance(), ROLENAME_ORIGINAL);
 439          $rolesnames = array();
 440          foreach ($roles as $role) {
 441              $rolesnames[$role->id] = $role->localname;
 442          }
 443          return $rolesnames;
 444      }
 445      /**
 446       * Get all the permissions name in a simple array.
 447       *
 448       * @return array Array of permissions name.
 449       */
 450      protected static function get_permissions_name() {
 451          $strpermissions = array(
 452              CAP_INHERIT => get_string('inherit', 'role'),
 453              CAP_ALLOW => get_string('allow', 'role'),
 454              CAP_PREVENT => get_string('prevent', 'role'),
 455              CAP_PROHIBIT => get_string('prohibit', 'role')
 456          );
 457          return $strpermissions;
 458      }
 459  }