Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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.

Differences Between: [Versions 39 and 402] [Versions 39 and 403]

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