Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
   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_tag.
  19   *
  20   * @package    core_tag
  21   * @copyright  2018 Zig Tan <zig@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_tag\privacy;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use \core_privacy\local\metadata\collection;
  30  use core_privacy\local\request\approved_contextlist;
  31  use core_privacy\local\request\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 Subsystem implementation for core_tag.
  39   *
  40   * @copyright  2018 Zig Tan <zig@moodle.com>
  41   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class provider implements
  44          // Tags store user data.
  45          \core_privacy\local\metadata\provider,
  46  
  47          // The tag subsystem provides data to other components.
  48          \core_privacy\local\request\subsystem\plugin_provider,
  49  
  50          // This plugin is capable of determining which users have data within it.
  51          \core_privacy\local\request\core_userlist_provider,
  52  
  53          // The tag subsystem may have data that belongs to this user.
  54          \core_privacy\local\request\plugin\provider,
  55  
  56          \core_privacy\local\request\shared_userlist_provider
  57      {
  58  
  59      /**
  60       * Returns meta data about this system.
  61       *
  62       * @param   collection     $collection The initialised collection to add items to.
  63       * @return  collection     A listing of user data stored through this system.
  64       */
  65      public static function get_metadata(collection $collection) : collection {
  66          // The table 'tag' contains data that a user has entered.
  67          // It is currently linked with a userid, but this field will hopefulyl go away.
  68          // Note: The userid is not necessarily 100% accurate. See MDL-61555.
  69          $collection->add_database_table('tag', [
  70                  'name' => 'privacy:metadata:tag:name',
  71                  'rawname' => 'privacy:metadata:tag:rawname',
  72                  'description' => 'privacy:metadata:tag:description',
  73                  'flag' => 'privacy:metadata:tag:flag',
  74                  'timemodified' => 'privacy:metadata:tag:timemodified',
  75                  'userid' => 'privacy:metadata:tag:userid',
  76              ], 'privacy:metadata:tag');
  77  
  78          // The table 'tag_instance' contains user data.
  79          // It links the user of a specific tag, to the item which is tagged.
  80          // In some cases the userid who 'owns' the tag is also stored.
  81          $collection->add_database_table('tag_instance', [
  82                  'tagid' => 'privacy:metadata:taginstance:tagid',
  83                  'ordering' => 'privacy:metadata:taginstance:ordering',
  84                  'timecreated' => 'privacy:metadata:taginstance:timecreated',
  85                  'timemodified' => 'privacy:metadata:taginstance:timemodified',
  86                  'tiuserid' => 'privacy:metadata:taginstance:tiuserid',
  87              ], 'privacy:metadata:taginstance');
  88  
  89          // The table 'tag_area' does not contain any specific user data.
  90          // It links components and item types to collections and describes how they can be associated.
  91  
  92          // The table 'tag_coll' does not contain any specific user data.
  93          // It describes a list of tag collections configured by the administrator.
  94  
  95          // The table 'tag_correlation' does not contain any user data.
  96          // It is a cache for other data already stored.
  97  
  98          return $collection;
  99      }
 100  
 101      /**
 102       * Store all tags which match the specified component, itemtype, and itemid.
 103       *
 104       * In most situations you will want to specify $onlyuser as false.
 105       * This will fetch only tags where the user themselves set the tag, or where tags are a shared resource.
 106       *
 107       * If you specify $onlyuser as true, only the tags created by that user will be included.
 108       *
 109       * @param   int         $userid The user whose information is to be exported
 110       * @param   \context    $context The context to export for
 111       * @param   array       $subcontext The subcontext within the context to export this information
 112       * @param   string      $component The component to fetch data from
 113       * @param   string      $itemtype The itemtype that the data was exported in within the component
 114       * @param   int         $itemid The itemid within that tag
 115       * @param   bool        $onlyuser Whether to only export ratings that the current user has made, or all tags
 116       */
 117      public static function export_item_tags(
 118          int $userid,
 119          \context $context,
 120          array $subcontext,
 121          string $component,
 122          string $itemtype,
 123          int $itemid,
 124          bool $onlyuser = false
 125      ) {
 126          global $DB;
 127  
 128          // Ignore mdl_tag.userid here because it only reflects the user who originally created the tag.
 129          $sql = "SELECT
 130                      t.rawname
 131                    FROM {tag} t
 132              INNER JOIN {tag_instance} ti ON ti.tagid = t.id
 133                   WHERE ti.component = :component
 134                     AND ti.itemtype = :itemtype
 135                     AND ti.itemid = :itemid
 136                     ";
 137  
 138          if ($onlyuser) {
 139              $sql .= "AND ti.tiuserid = :userid";
 140          } else {
 141              $sql .= "AND (ti.tiuserid = 0 OR ti.tiuserid = :userid)";
 142          }
 143  
 144          $params = [
 145              'component' => $component,
 146              'itemtype' => $itemtype,
 147              'itemid' => $itemid,
 148              'userid' => $userid,
 149          ];
 150  
 151          if ($tags = $DB->get_fieldset_sql($sql, $params)) {
 152              $writer = \core_privacy\local\request\writer::with_context($context)
 153                  ->export_related_data($subcontext, 'tags', $tags);
 154          }
 155      }
 156  
 157      /**
 158       * Deletes all tag instances for given context, component, itemtype, itemid
 159       *
 160       * In most situations you will want to specify $userid as null. Per-user tag instances
 161       * are possible in Tags API, however there are no components or standard plugins that actually use them.
 162       *
 163       * @param   \context    $context The context to export for
 164       * @param   string      $component Tagarea component
 165       * @param   string      $itemtype Tagarea item type
 166       * @param   int         $itemid The itemid within that component and itemtype (optional)
 167       * @param   int         $userid Only delete tag instances made by this user, per-user tags must be enabled for the tagarea
 168       */
 169      public static function delete_item_tags(\context $context, $component, $itemtype,
 170              $itemid = null, $userid = null) {
 171          global $DB;
 172          $params = ['contextid' => $context->id, 'component' => $component, 'itemtype' => $itemtype];
 173          if ($itemid) {
 174              $params['itemid'] = $itemid;
 175          }
 176          if ($userid) {
 177              $params['tiuserid'] = $userid;
 178          }
 179          $DB->delete_records('tag_instance', $params);
 180      }
 181  
 182      /**
 183       * Deletes all tag instances for given context, component, itemtype using subquery for itemids
 184       *
 185       * In most situations you will want to specify $userid as null. Per-user tag instances
 186       * are possible in Tags API, however there are no components or standard plugins that actually use them.
 187       *
 188       * @param   \context    $context The context to export for
 189       * @param   string      $component Tagarea component
 190       * @param   string      $itemtype Tagarea item type
 191       * @param   string      $itemidstest an SQL fragment that the itemid must match. Used
 192       *      in the query like WHERE itemid $itemidstest. Must use named parameters,
 193       *      and may not use named parameters called contextid, component or itemtype.
 194       * @param array $params any query params used by $itemidstest.
 195       */
 196      public static function delete_item_tags_select(\context $context, $component, $itemtype,
 197                                              $itemidstest, $params = []) {
 198          global $DB;
 199          $params += ['contextid' => $context->id, 'component' => $component, 'itemtype' => $itemtype];
 200          $DB->delete_records_select('tag_instance',
 201              'contextid = :contextid AND component = :component AND itemtype = :itemtype AND itemid ' . $itemidstest,
 202              $params);
 203      }
 204  
 205      /**
 206       * Get the list of contexts that contain user information for the specified user.
 207       *
 208       * @param   int         $userid     The user to search.
 209       * @return  contextlist   $contextlist  The contextlist containing the list of contexts used in this plugin.
 210       */
 211      public static function get_contexts_for_userid(int $userid) : contextlist {
 212          $contextlist = new contextlist();
 213          $contextlist->add_from_sql("SELECT c.id
 214                    FROM {context} c
 215                    JOIN {tag} t ON t.userid = :userid
 216                   WHERE contextlevel = :contextlevel",
 217              ['userid' => $userid, 'contextlevel' => CONTEXT_SYSTEM]);
 218          return $contextlist;
 219      }
 220  
 221      /**
 222       * Get the list of users within a specific context.
 223       *
 224       * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
 225       */
 226      public static function get_users_in_context(userlist $userlist) {
 227          $context = $userlist->get_context();
 228  
 229          if (!$context instanceof \context_system) {
 230              return;
 231          }
 232  
 233          $sql = "SELECT userid
 234                    FROM {tag}";
 235  
 236          $userlist->add_from_sql('userid', $sql, []);
 237      }
 238  
 239      /**
 240       * Export all user data for the specified user, in the specified contexts.
 241       *
 242       * @param   approved_contextlist    $contextlist    The approved contexts to export information for.
 243       */
 244      public static function export_user_data(approved_contextlist $contextlist) {
 245          global $DB;
 246          $context = \context_system::instance();
 247          if (!$contextlist->count() || !in_array($context->id, $contextlist->get_contextids())) {
 248              return;
 249          }
 250  
 251          $user = $contextlist->get_user();
 252          $sql = "SELECT id, userid, tagcollid, name, rawname, isstandard, description, descriptionformat, flag, timemodified
 253              FROM {tag} WHERE userid = ?";
 254          $rs = $DB->get_recordset_sql($sql, [$user->id]);
 255          foreach ($rs as $record) {
 256              $subcontext = [get_string('tags', 'tag'), $record->id];
 257              $tag = (object)[
 258                  'id' => $record->id,
 259                  'userid' => transform::user($record->userid),
 260                  'name' => $record->name,
 261                  'rawname' => $record->rawname,
 262                  'isstandard' => transform::yesno($record->isstandard),
 263                  'description' => writer::with_context($context)->rewrite_pluginfile_urls($subcontext,
 264                      'tag', 'description', $record->id, strval($record->description)),
 265                  'descriptionformat' => $record->descriptionformat,
 266                  'flag' => $record->flag,
 267                  'timemodified' => transform::datetime($record->timemodified),
 268  
 269              ];
 270              writer::with_context($context)->export_data($subcontext, $tag);
 271              writer::with_context($context)->export_area_files($subcontext, 'tag', 'description', $record->id);
 272          }
 273          $rs->close();
 274      }
 275  
 276      /**
 277       * Delete all data for all users in the specified context.
 278       *
 279       * We do not delete tag instances in this method - this should be done by the components that define tagareas.
 280       * We only delete tags themselves in case of system context.
 281       *
 282       * @param context $context   The specific context to delete data for.
 283       */
 284      public static function delete_data_for_all_users_in_context(\context $context) {
 285          global $DB;
 286          // Tags can only be defined in system context.
 287          if ($context->id == \context_system::instance()->id) {
 288              $DB->delete_records('tag_instance');
 289              $DB->delete_records('tag', []);
 290          }
 291      }
 292  
 293      /**
 294       * Delete multiple users within a single context.
 295       *
 296       * @param approved_userlist $userlist The approved context and user information to delete information for.
 297       */
 298      public static function delete_data_for_users(approved_userlist $userlist) {
 299          global $DB;
 300  
 301          $context = $userlist->get_context();
 302  
 303          if ($context instanceof \context_system) {
 304              // Do not delete tags themselves in case they are used by somebody else.
 305              // If the user is the only one using the tag, it will be automatically deleted anyway during the
 306              // next cron cleanup.
 307              list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
 308              $DB->set_field_select('tag', 'userid', 0, "userid {$usersql}", $userparams);
 309          }
 310      }
 311  
 312      /**
 313       * Delete all user data for the specified user, in the specified contexts.
 314       *
 315       * @param   approved_contextlist    $contextlist    The approved contexts and user information to delete information for.
 316       */
 317      public static function delete_data_for_user(approved_contextlist $contextlist) {
 318          global $DB;
 319          $context = \context_system::instance();
 320          if (!$contextlist->count() || !in_array($context->id, $contextlist->get_contextids())) {
 321              return;
 322          }
 323  
 324          // Do not delete tags themselves in case they are used by somebody else.
 325          // If the user is the only one using the tag, it will be automatically deleted anyway during the next cron cleanup.
 326          $DB->set_field_select('tag', 'userid', 0, 'userid = ?', [$contextlist->get_user()->id]);
 327      }
 328  }