Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   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  namespace mod_h5pactivity\privacy;
  18  
  19  use core_privacy\local\metadata\collection;
  20  use core_privacy\local\request\approved_contextlist;
  21  use core_privacy\local\request\approved_userlist;
  22  use core_privacy\local\request\contextlist;
  23  use core_privacy\local\request\helper;
  24  use core_privacy\local\request\transform;
  25  use core_privacy\local\request\userlist;
  26  use core_privacy\local\request\writer;
  27  use stdClass;
  28  
  29  /**
  30   * Privacy API implementation for the H5P activity plugin.
  31   *
  32   * @package    mod_h5pactivity
  33   * @category   privacy
  34   * @copyright  2020 Ferran Recio <ferran@moodle.com>
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class provider implements
  38          \core_privacy\local\metadata\provider,
  39          \core_privacy\local\request\core_userlist_provider,
  40          \core_privacy\local\request\plugin\provider {
  41  
  42      /**
  43       * Return the fields which contain personal data.
  44       *
  45       * @param   collection $collection The initialised collection to add items to.
  46       * @return  collection A listing of user data stored through this system.
  47       */
  48      public static function get_metadata(collection $collection) : collection {
  49          $collection->add_database_table('h5pactivity_attempts', [
  50                  'userid' => 'privacy:metadata:userid',
  51                  'attempt' => 'privacy:metadata:attempt',
  52                  'timecreated' => 'privacy:metadata:timecreated',
  53                  'timemodified' => 'privacy:metadata:timemodified',
  54                  'rawscore' => 'privacy:metadata:rawscore',
  55              ], 'privacy:metadata:xapi_track');
  56  
  57          $collection->add_database_table('h5pactivity_attempts_results', [
  58                  'attempt' => 'privacy:metadata:attempt',
  59                  'timecreated' => 'privacy:metadata:timecreated',
  60                  'rawscore' => 'privacy:metadata:rawscore',
  61              ], 'privacy:metadata:xapi_track_results');
  62  
  63          $collection->add_subsystem_link('core_xapi', [], 'privacy:metadata:xapisummary');
  64  
  65          return $collection;
  66      }
  67  
  68      /**
  69       * Get the list of contexts that contain user information for the specified user.
  70       *
  71       * @param int $userid The user to search.
  72       * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
  73       */
  74      public static function get_contexts_for_userid(int $userid) : contextlist {
  75          $sql = "SELECT ctx.id
  76                    FROM {h5pactivity_attempts} ss
  77                    JOIN {modules} m
  78                      ON m.name = :activityname
  79                    JOIN {course_modules} cm
  80                      ON cm.instance = ss.h5pactivityid
  81                     AND cm.module = m.id
  82                    JOIN {context} ctx
  83                      ON ctx.instanceid = cm.id
  84                     AND ctx.contextlevel = :modlevel
  85                   WHERE ss.userid = :userid";
  86  
  87          $params = ['activityname' => 'h5pactivity', 'modlevel' => CONTEXT_MODULE, 'userid' => $userid];
  88          $contextlist = new contextlist();
  89          $contextlist->add_from_sql($sql, $params);
  90  
  91          \core_xapi\privacy\provider::add_contexts_for_userid($contextlist, $userid, 'mod_h5pactivity');
  92  
  93          return $contextlist;
  94      }
  95  
  96      /**
  97       * Get the list of users who have data within a context.
  98       *
  99       * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
 100       */
 101      public static function get_users_in_context(userlist $userlist) {
 102          $context = $userlist->get_context();
 103  
 104          if (!is_a($context, \context_module::class)) {
 105              return;
 106          }
 107  
 108          $sql = "SELECT ss.userid
 109                    FROM {h5pactivity_attempts} ss
 110                    JOIN {modules} m
 111                      ON m.name = 'h5pactivity'
 112                    JOIN {course_modules} cm
 113                      ON cm.instance = ss.h5pactivityid
 114                     AND cm.module = m.id
 115                    JOIN {context} ctx
 116                      ON ctx.instanceid = cm.id
 117                     AND ctx.contextlevel = :modlevel
 118                   WHERE ctx.id = :contextid";
 119  
 120          $params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id];
 121  
 122          $userlist->add_from_sql('userid', $sql, $params);
 123  
 124          \core_xapi\privacy\provider::add_userids_for_context($userlist);
 125      }
 126  
 127      /**
 128       * Export all user data for the specified user, in the specified contexts.
 129       *
 130       * @param approved_contextlist $contextlist The approved contexts to export information for.
 131       */
 132      public static function export_user_data(approved_contextlist $contextlist) {
 133          global $DB;
 134  
 135          // Remove contexts different from CONTEXT_MODULE.
 136          $contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
 137              if ($context->contextlevel == CONTEXT_MODULE) {
 138                  $carry[] = $context->id;
 139              }
 140              return $carry;
 141          }, []);
 142  
 143          if (empty($contexts)) {
 144              return;
 145          }
 146  
 147          $user = $contextlist->get_user();
 148          $userid = $user->id;
 149          // Get H5P attempts data.
 150          foreach ($contexts as $contextid) {
 151              $context = \context::instance_by_id($contextid);
 152              $data = helper::get_context_data($context, $user);
 153              writer::with_context($context)->export_data([], $data);
 154              helper::export_context_files($context, $user);
 155  
 156              // Get user's xAPI state data for the particular context.
 157              $state = \core_xapi\privacy\provider::get_xapi_states_for_user($contextlist->get_user()->id,
 158                      'mod_h5pactivity', $context->instanceid);
 159              if ($state) {
 160                  // If the activity has xAPI state data by the user, include it in the export.
 161                  writer::with_context($context)->export_data(
 162                          [get_string('privacy:xapistate', 'core_xapi')], (object) $state);
 163              }
 164  
 165          }
 166  
 167          // Get attempts track data.
 168          list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
 169          $sql = "SELECT har.id,
 170                         ha.attempt,
 171                         har.description,
 172                         har.interactiontype,
 173                         har.response,
 174                         har.additionals,
 175                         har.rawscore,
 176                         har.maxscore,
 177                         har.duration,
 178                         har.timecreated,
 179                         ctx.id as contextid
 180                    FROM {h5pactivity_attempts_results} har
 181                    JOIN {h5pactivity_attempts} ha
 182                      ON har.attemptid = ha.id
 183                    JOIN {course_modules} cm
 184                      ON cm.instance = ha.h5pactivityid
 185                    JOIN {context} ctx
 186                      ON ctx.instanceid = cm.id
 187                   WHERE ctx.id $insql
 188                     AND ha.userid = :userid";
 189          $params = array_merge($inparams, ['userid' => $userid]);
 190  
 191          $alldata = [];
 192          $attemptsdata = $DB->get_recordset_sql($sql, $params);
 193          foreach ($attemptsdata as $track) {
 194              $alldata[$track->contextid][$track->attempt][] = (object)[
 195                      'description' => $track->description,
 196                      'response' => $track->response,
 197                      'interactiontype' => $track->interactiontype,
 198                      'additionals' => $track->additionals,
 199                      'rawscore' => $track->rawscore,
 200                      'maxscore' => $track->maxscore,
 201                      'duration' => $track->duration,
 202                      'timecreated' => transform::datetime($track->timecreated),
 203                  ];
 204          }
 205          $attemptsdata->close();
 206  
 207          // The result data is organised in:
 208          // {Course name}/{H5P activity name}/{My attempts}/{Attempt X}/data.json
 209          // where X is the attempt number.
 210          array_walk($alldata, function($attemptsdata, $contextid) {
 211              $context = \context::instance_by_id($contextid);
 212              array_walk($attemptsdata, function($data, $attempt) use ($context) {
 213                  $subcontext = [
 214                      get_string('myattempts', 'mod_h5pactivity'),
 215                      get_string('attempt', 'mod_h5pactivity'). " $attempt"
 216                  ];
 217                  writer::with_context($context)->export_data(
 218                      $subcontext,
 219                      (object)['results' => $data]
 220                  );
 221              });
 222          });
 223      }
 224  
 225      /**
 226       * Delete all user data which matches the specified context.
 227       *
 228       * @param \context $context A user context.
 229       */
 230      public static function delete_data_for_all_users_in_context(\context $context) {
 231          // This should not happen, but just in case.
 232          if ($context->contextlevel != CONTEXT_MODULE) {
 233              return;
 234          }
 235  
 236          $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid);
 237          if (!$cm) {
 238              // Only h5pactivity module will be handled.
 239              return;
 240          }
 241  
 242          self::delete_all_attempts($cm);
 243  
 244          // Delete xAPI state data.
 245          \core_xapi\privacy\provider::delete_states_for_all_users($context, 'mod_h5pactivity');
 246  
 247      }
 248  
 249      /**
 250       * Delete all user data for the specified user, in the specified contexts.
 251       *
 252       * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
 253       */
 254      public static function delete_data_for_user(approved_contextlist $contextlist) {
 255  
 256          foreach ($contextlist as $context) {
 257              if ($context->contextlevel != CONTEXT_MODULE) {
 258                  continue;
 259              }
 260  
 261              $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid);
 262              if (!$cm) {
 263                  // Only h5pactivity module will be handled.
 264                  continue;
 265              }
 266  
 267              $user = $contextlist->get_user();
 268  
 269              self::delete_all_attempts($cm, $user);
 270  
 271              // Delete xAPI state data.
 272              \core_xapi\privacy\provider::delete_states_for_user($contextlist, 'mod_h5pactivity');
 273          }
 274      }
 275  
 276      /**
 277       * Delete multiple users within a single context.
 278       *
 279       * @param   approved_userlist       $userlist The approved context and user information to delete information for.
 280       */
 281      public static function delete_data_for_users(approved_userlist $userlist) {
 282  
 283          $context = $userlist->get_context();
 284  
 285          if (!is_a($context, \context_module::class)) {
 286              return;
 287          }
 288  
 289          $cm = get_coursemodule_from_id('h5pactivity', $context->instanceid);
 290          if (!$cm) {
 291              // Only h5pactivity module will be handled.
 292              return;
 293          }
 294  
 295          $userids = $userlist->get_userids();
 296  
 297          foreach ($userids as $userid) {
 298              self::delete_all_attempts ($cm, (object)['id' => $userid]);
 299          }
 300  
 301          // Delete xAPI states data.
 302          \core_xapi\privacy\provider::delete_states_for_userlist($userlist);
 303  
 304      }
 305  
 306      /**
 307       * Wipe all attempt data for specific course_module and an optional user.
 308       *
 309       * @param stdClass $cm a course_module record
 310       * @param stdClass $user a user record
 311       */
 312      private static function delete_all_attempts(stdClass $cm, stdClass $user = null): void {
 313          global $DB;
 314  
 315          $where = 'a.h5pactivityid = :h5pactivityid';
 316          $conditions = ['h5pactivityid' => $cm->instance];
 317          if (!empty($user)) {
 318              $where .= ' AND a.userid = :userid';
 319              $conditions['userid'] = $user->id;
 320          }
 321  
 322          $DB->delete_records_select('h5pactivity_attempts_results', "attemptid IN (
 323                  SELECT a.id
 324                  FROM {h5pactivity_attempts} a
 325                  WHERE $where)", $conditions);
 326  
 327          $DB->delete_records('h5pactivity_attempts', $conditions);
 328      }
 329  }