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   * Data provider.
  19   *
  20   * @package    logstore_legacy
  21   * @copyright  2018 Frédéric Massart
  22   * @author     Frédéric Massart <fred@branchup.tech>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace logstore_legacy\privacy;
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use context;
  30  use core_privacy\local\metadata\collection;
  31  use core_privacy\local\request\approved_contextlist;
  32  use core_privacy\local\request\contextlist;
  33  use core_privacy\local\request\transform;
  34  use core_privacy\local\request\writer;
  35  use tool_log\local\privacy\helper;
  36  
  37  /**
  38   * Data provider class.
  39   *
  40   * @package    logstore_legacy
  41   * @copyright  2018 Frédéric Massart
  42   * @author     Frédéric Massart <fred@branchup.tech>
  43   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  class provider implements
  46      \core_privacy\local\metadata\provider,
  47      \tool_log\local\privacy\logstore_provider,
  48      \tool_log\local\privacy\logstore_userlist_provider {
  49  
  50      /**
  51       * Returns metadata.
  52       *
  53       * @param collection $collection The initialised collection to add items to.
  54       * @return collection A listing of user data stored through this system.
  55       */
  56      public static function get_metadata(collection $collection) : collection {
  57          $collection->add_external_location_link('log', [
  58              'time' => 'privacy:metadata:log:time',
  59              'userid' => 'privacy:metadata:log:userid',
  60              'ip' => 'privacy:metadata:log:ip',
  61              'action' => 'privacy:metadata:log:action',
  62              'url' => 'privacy:metadata:log:url',
  63              'info' => 'privacy:metadata:log:info',
  64          ], 'privacy:metadata:log');
  65          return $collection;
  66      }
  67  
  68      /**
  69       * Add contexts that contain user information for the specified user.
  70       *
  71       * @param contextlist $contextlist The contextlist to add the contexts to.
  72       * @param int $userid The user to find the contexts for.
  73       * @return void
  74       */
  75      public static function add_contexts_for_userid(contextlist $contextlist, $userid) {
  76          $sql = "
  77              SELECT ctx.id
  78                FROM {context} ctx
  79                JOIN {log} l
  80                  ON (l.cmid = 0 AND l.course = ctx.instanceid AND ctx.contextlevel = :courselevel)
  81                  OR (l.cmid > 0 AND l.cmid = ctx.instanceid AND ctx.contextlevel = :modulelevel)
  82                  OR (l.course <= 0 AND ctx.id = :syscontextid)
  83               WHERE l.userid = :userid";
  84          $params = [
  85              'courselevel' => CONTEXT_COURSE,
  86              'modulelevel' => CONTEXT_MODULE,
  87              'syscontextid' => SYSCONTEXTID,
  88              'userid' => $userid,
  89          ];
  90          $contextlist->add_from_sql($sql, $params);
  91      }
  92  
  93      /**
  94       * Add user IDs that contain user information for the specified context.
  95       *
  96       * @param \core_privacy\local\request\userlist $userlist The userlist to add the users to.
  97       * @return void
  98       */
  99      public static function add_userids_for_context(\core_privacy\local\request\userlist $userlist) {
 100          $context = $userlist->get_context();
 101          list($insql, $params) = static::get_sql_where_from_contexts([$context]);
 102  
 103          $sql = "SELECT l.userid
 104                    FROM {log} l
 105                   WHERE $insql";
 106          $userlist->add_from_sql('userid', $sql, $params);
 107      }
 108  
 109      /**
 110       * Export all user data for the specified user, in the specified contexts.
 111       *
 112       * @param approved_contextlist $contextlist The approved contexts to export information for.
 113       */
 114      public static function export_user_data(approved_contextlist $contextlist) {
 115          global $DB;
 116  
 117          $userid = $contextlist->get_user()->id;
 118          list($insql, $inparams) = static::get_sql_where_from_contexts($contextlist->get_contexts());
 119          if (empty($insql)) {
 120              return;
 121          }
 122          $sql = "userid = :userid AND $insql";
 123          $params = array_merge($inparams, ['userid' => $userid]);
 124  
 125          $path = [get_string('privacy:path:logs', 'tool_log'), get_string('pluginname', 'logstore_legacy')];
 126          $flush = function($lastcontextid, $data) use ($path) {
 127              $context = context::instance_by_id($lastcontextid);
 128              writer::with_context($context)->export_data($path, (object) ['logs' => $data]);
 129          };
 130  
 131          $lastcontextid = null;
 132          $data = [];
 133          $recordset = $DB->get_recordset_select('log', $sql, $params, 'course, cmid, time, id');
 134          foreach ($recordset as $record) {
 135              $event = \logstore_legacy\event\legacy_logged::restore_legacy($record);
 136              $context = $event->get_context();
 137              if ($lastcontextid && $lastcontextid != $context->id) {
 138                  $flush($lastcontextid, $data);
 139                  $data = [];
 140              }
 141  
 142              $extra = $event->get_logextra();
 143              $data[] = [
 144                  'name' => $event->get_name(),
 145                  'description' => $event->get_description(),
 146                  'timecreated' => transform::datetime($event->timecreated),
 147                  'ip' => $extra['ip'],
 148                  'origin' => helper::transform_origin($extra['origin']),
 149              ];
 150  
 151              $lastcontextid = $context->id;
 152          }
 153          if ($lastcontextid) {
 154              $flush($lastcontextid, $data);
 155          }
 156          $recordset->close();
 157      }
 158  
 159      /**
 160       * Delete all data for all users in the specified context.
 161       *
 162       * @param context $context The specific context to delete data for.
 163       */
 164      public static function delete_data_for_all_users_in_context(context $context) {
 165          global $DB;
 166          list($sql, $params) = static::get_sql_where_from_contexts([$context]);
 167          if (empty($sql)) {
 168              return;
 169          }
 170          $DB->delete_records_select('log', $sql, $params);
 171      }
 172  
 173      /**
 174       * Delete all user data for the specified user, in the specified contexts.
 175       *
 176       * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
 177       */
 178      public static function delete_data_for_user(approved_contextlist $contextlist) {
 179          global $DB;
 180          list($sql, $params) = static::get_sql_where_from_contexts($contextlist->get_contexts());
 181          if (empty($sql)) {
 182              return;
 183          }
 184          $userid = $contextlist->get_user()->id;
 185          $DB->delete_records_select('log', "$sql AND userid = :userid", array_merge($params, ['userid' => $userid]));
 186      }
 187  
 188  
 189      /**
 190       * Delete all data for a list of users in the specified context.
 191       *
 192       * @param \core_privacy\local\request\approved_userlist $userlist The specific context and users to delete data for.
 193       * @return void
 194       */
 195      public static function delete_data_for_userlist(\core_privacy\local\request\approved_userlist $userlist) {
 196          global $DB;
 197          list($sql, $params) = static::get_sql_where_from_contexts([$userlist->get_context()]);
 198          if (empty($sql)) {
 199              return;
 200          }
 201          list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
 202          $params = array_merge($params, $userparams);
 203          $DB->delete_records_select('log', "$sql AND userid $usersql", $params);
 204      }
 205  
 206      /**
 207       * Get an SQL where statement from a list of contexts.
 208       *
 209       * @param array $contexts The contexts.
 210       * @return array [$sql, $params]
 211       */
 212      protected static function get_sql_where_from_contexts(array $contexts) {
 213          global $DB;
 214  
 215          $sorted = array_reduce($contexts, function ($carry, $context) {
 216              $level = $context->contextlevel;
 217              if ($level == CONTEXT_MODULE || $level == CONTEXT_COURSE) {
 218                  $carry[$level][] = $context->instanceid;
 219              } else if ($level == CONTEXT_SYSTEM) {
 220                  $carry[$level] = $context->id;
 221              }
 222              return $carry;
 223          }, [
 224              CONTEXT_COURSE => [],
 225              CONTEXT_MODULE => [],
 226              CONTEXT_SYSTEM => null,
 227          ]);
 228  
 229          $sqls = [];
 230          $params = [];
 231  
 232          if (!empty($sorted[CONTEXT_MODULE])) {
 233              list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_MODULE], SQL_PARAMS_NAMED);
 234              $sqls[] = "cmid $insql";
 235              $params = array_merge($params, $inparams);
 236          }
 237  
 238          if (!empty($sorted[CONTEXT_COURSE])) {
 239              list($insql, $inparams) = $DB->get_in_or_equal($sorted[CONTEXT_COURSE], SQL_PARAMS_NAMED);
 240  
 241              $sqls[] = "cmid = 0 AND course $insql";
 242              $params = array_merge($params, $inparams);
 243          }
 244  
 245          if (!empty($sorted[CONTEXT_SYSTEM])) {
 246              $sqls[] = "course <= 0";
 247          }
 248  
 249          if (empty($sqls)) {
 250              return [null, null];
 251          }
 252  
 253          return ['((' . implode(') OR (', $sqls) . '))', $params];
 254      }
 255  }