Search moodle.org's
Developer Documentation

See Release Notes

  • 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.
   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 report_stats.
  19   *
  20   * @package    report_stats
  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 report_stats\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\userlist;
  33  use \core_privacy\local\request\approved_userlist;
  34  
  35  /**
  36   * Privacy Subsystem for report_stats implementing provider.
  37   *
  38   * @copyright  2018 Zig Tan <zig@moodle.com>
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   */
  41  class provider implements
  42          \core_privacy\local\metadata\provider,
  43          \core_privacy\local\request\core_userlist_provider,
  44          \core_privacy\local\request\subsystem\provider{
  45  
  46      /**
  47       * Returns information about the user data stored in this component.
  48       *
  49       * @param  collection $collection A list of information about this component
  50       * @return collection The collection object filled out with information about this component.
  51       */
  52      public static function get_metadata(collection $collection) : collection {
  53          $statsuserdaily = [
  54              'courseid' => 'privacy:metadata:courseid',
  55              'userid' => 'privacy:metadata:userid',
  56              'roleid' => 'privacy:metadata:roleid',
  57              'timeend' => 'privacy:metadata:timeend',
  58              'statsreads' => 'privacy:metadata:statsreads',
  59              'statswrites' => 'privacy:metadata:statswrites',
  60              'stattype' => 'privacy:metadata:stattype'
  61          ];
  62  
  63          $statsuserweekly = [
  64              'courseid' => 'privacy:metadata:courseid',
  65              'userid' => 'privacy:metadata:userid',
  66              'roleid' => 'privacy:metadata:roleid',
  67              'timeend' => 'privacy:metadata:timeend',
  68              'statsreads' => 'privacy:metadata:statsreads',
  69              'statswrites' => 'privacy:metadata:statswrites',
  70              'stattype' => 'privacy:metadata:stattype'
  71          ];
  72  
  73          $statsusermonthly = [
  74              'courseid' => 'privacy:metadata:courseid',
  75              'userid' => 'privacy:metadata:userid',
  76              'roleid' => 'privacy:metadata:roleid',
  77              'timeend' => 'privacy:metadata:timeend',
  78              'statsreads' => 'privacy:metadata:statsreads',
  79              'statswrites' => 'privacy:metadata:statswrites',
  80              'stattype' => 'privacy:metadata:stattype'
  81          ];
  82          $collection->add_database_table('stats_user_daily', $statsuserdaily, 'privacy:metadata:statssummary');
  83          $collection->add_database_table('stats_user_weekly', $statsuserweekly, 'privacy:metadata:statssummary');
  84          $collection->add_database_table('stats_user_monthly', $statsusermonthly, 'privacy:metadata:statssummary');
  85          return $collection;
  86      }
  87  
  88      /**
  89       * Get the list of contexts that contain user information for the specified user.
  90       *
  91       * @param   int $userid The user to search.
  92       * @return  contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
  93       */
  94      public static function get_contexts_for_userid(int $userid) : contextlist {
  95          $params = ['userid' => $userid, 'contextcourse' => CONTEXT_COURSE];
  96          $sql = "SELECT ctx.id
  97                  FROM {context} ctx
  98                  JOIN {stats_user_daily} sud ON sud.courseid = ctx.instanceid AND sud.userid = :userid
  99                  WHERE ctx.contextlevel = :contextcourse";
 100  
 101          $contextlist = new contextlist();
 102          $contextlist->add_from_sql($sql, $params);
 103  
 104          $sql = "SELECT ctx.id
 105                  FROM {context} ctx
 106                  JOIN {stats_user_weekly} suw ON suw.courseid = ctx.instanceid AND suw.userid = :userid
 107                  WHERE ctx.contextlevel = :contextcourse";
 108          $contextlist->add_from_sql($sql, $params);
 109  
 110          $sql = "SELECT ctx.id
 111                  FROM {context} ctx
 112                  JOIN {stats_user_monthly} sum ON sum.courseid = ctx.instanceid AND sum.userid = :userid
 113                  WHERE ctx.contextlevel = :contextcourse";
 114          $contextlist->add_from_sql($sql, $params);
 115  
 116          return $contextlist;
 117      }
 118  
 119      /**
 120       * Get the list of users within a specific context.
 121       *
 122       * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
 123       */
 124      public static function get_users_in_context(userlist $userlist) {
 125          $context = $userlist->get_context();
 126  
 127          if (!$context instanceof \context_course) {
 128              return;
 129          }
 130  
 131          $params = ['courseid' => $context->instanceid];
 132  
 133          $sql = "SELECT userid FROM {stats_user_daily} WHERE courseid = :courseid";
 134          $userlist->add_from_sql('userid', $sql, $params);
 135  
 136          $sql = "SELECT userid FROM {stats_user_weekly} WHERE courseid = :courseid";
 137          $userlist->add_from_sql('userid', $sql, $params);
 138  
 139          $sql = "SELECT userid FROM {stats_user_monthly} WHERE courseid = :courseid";
 140          $userlist->add_from_sql('userid', $sql, $params);
 141      }
 142  
 143      /**
 144       * Export all user data for the specified user, in the specified contexts.
 145       *
 146       * @param approved_contextlist $contextlist The approved contexts to export information for.
 147       */
 148      public static function export_user_data(approved_contextlist $contextlist) {
 149          global $DB;
 150  
 151          // Some sneeky person might have sent us the wrong context list. We should check.
 152          if ($contextlist->get_component() != 'report_stats') {
 153              return;
 154          }
 155  
 156          // Got to check that someone hasn't foolishly added a context between creating the context list and then filtering down
 157          // to an approved context.
 158          $contexts = array_filter($contextlist->get_contexts(), function($context) {
 159              if ($context->contextlevel == CONTEXT_COURSE) {
 160                  return $context;
 161              }
 162          });
 163  
 164          $tables = [
 165              'stats_user_daily' => get_string('privacy:dailypath', 'report_stats'),
 166              'stats_user_weekly' => get_string('privacy:weeklypath', 'report_stats'),
 167              'stats_user_monthly' => get_string('privacy:monthlypath', 'report_stats')
 168          ];
 169  
 170          $courseids = array_map(function($context) {
 171              return $context->instanceid;
 172          }, $contexts);
 173  
 174          foreach ($tables as $table => $path) {
 175  
 176              list($insql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
 177              $sql = "SELECT s.id, c.fullname, s.roleid, s.timeend, s.statsreads, s.statswrites, s.stattype, c.id as courseid
 178                        FROM {" . $table . "} s
 179                        JOIN {course} c ON s.courseid = c.id
 180                       WHERE s.userid = :userid AND c.id $insql
 181                       ORDER BY c.id ASC";
 182              $params['userid'] = $contextlist->get_user()->id;
 183              $records = $DB->get_records_sql($sql, $params);
 184  
 185              $statsrecords = [];
 186              foreach ($records as $record) {
 187                  $context = \context_course::instance($record->courseid);
 188                  if (!isset($statsrecords[$record->courseid])) {
 189                      $statsrecords[$record->courseid] = new \stdClass();
 190                      $statsrecords[$record->courseid]->context = $context;
 191                  }
 192                  $statsrecords[$record->courseid]->entries[] = [
 193                      'course' => format_string($record->fullname, true, ['context' => $context]),
 194                      'roleid' => $record->roleid,
 195                      'timeend' => \core_privacy\local\request\transform::datetime($record->timeend),
 196                      'statsreads' => $record->statsreads,
 197                      'statswrites' => $record->statswrites,
 198                      'stattype' => $record->stattype
 199                  ];
 200              }
 201              foreach ($statsrecords as $coursestats) {
 202                  \core_privacy\local\request\writer::with_context($coursestats->context)->export_data([$path],
 203                          (object) $coursestats->entries);
 204              }
 205          }
 206      }
 207  
 208      /**
 209       * Delete all data for all users in the specified context.
 210       *
 211       * @param context $context The specific context to delete data for.
 212       */
 213      public static function delete_data_for_all_users_in_context(\context $context) {
 214          // Check that this context is a course context.
 215          if ($context->contextlevel == CONTEXT_COURSE) {
 216              static::delete_stats($context->instanceid);
 217          }
 218      }
 219  
 220      /**
 221       * Delete all user data for the specified user, in the specified contexts.
 222       *
 223       * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
 224       */
 225      public static function delete_data_for_user(approved_contextlist $contextlist) {
 226          if ($contextlist->get_component() != 'report_stats') {
 227              return;
 228          }
 229          foreach ($contextlist->get_contexts() as $context) {
 230              if ($context->contextlevel == CONTEXT_COURSE) {
 231                  static::delete_stats($context->instanceid, $contextlist->get_user()->id);
 232              }
 233          }
 234      }
 235  
 236      /**
 237       * Delete multiple users within a single context.
 238       *
 239       * @param approved_userlist $userlist The approved context and user information to delete information for.
 240       */
 241      public static function delete_data_for_users(approved_userlist $userlist) {
 242          global $DB;
 243  
 244          $context = $userlist->get_context();
 245  
 246          if ($context instanceof \context_course) {
 247              list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
 248              $select = "courseid = :courseid AND userid {$usersql}";
 249              $params = ['courseid' => $context->instanceid] + $userparams;
 250  
 251              $DB->delete_records_select('stats_user_daily', $select, $params);
 252              $DB->delete_records_select('stats_user_weekly', $select, $params);
 253              $DB->delete_records_select('stats_user_monthly', $select, $params);
 254          }
 255      }
 256  
 257      /**
 258       * Deletes stats for a given course.
 259       *
 260       * @param int $courseid The course ID to delete the stats for.
 261       * @param int $userid Optionally a user id to delete records with.
 262       */
 263      protected static function delete_stats(int $courseid, int $userid = null) {
 264          global $DB;
 265          $params = (isset($userid)) ? ['courseid' => $courseid, 'userid' => $userid] : ['courseid' => $courseid];
 266          $DB->delete_records('stats_user_daily', $params);
 267          $DB->delete_records('stats_user_weekly', $params);
 268          $DB->delete_records('stats_user_monthly', $params);
 269      }
 270  }