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.

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

   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 class for requesting user data.
  19   *
  20   * @package    core_completion
  21   * @copyright  2018 Adrian Greeve <adrian@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_completion\privacy;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  use core_privacy\local\metadata\collection;
  30  use core_privacy\local\request\approved_userlist;
  31  use core_privacy\local\request\contextlist;
  32  use core_privacy\local\request\transform;
  33  use core_privacy\local\request\userlist;
  34  
  35  require_once($CFG->dirroot . '/comment/lib.php');
  36  
  37  /**
  38   * Privacy class for requesting user data.
  39   *
  40   * @package    core_completion
  41   * @copyright  2018 Adrian Greeve <adrian@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\subsystem\plugin_provider,
  47          \core_privacy\local\request\shared_userlist_provider
  48      {
  49  
  50      /**
  51       * Returns meta data about this system.
  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_database_table('course_completions', [
  58                  'userid' => 'privacy:metadata:userid',
  59                  'course' => 'privacy:metadata:course',
  60                  'timeenrolled' => 'privacy:metadata:timeenrolled',
  61                  'timestarted' => 'privacy:metadata:timestarted',
  62                  'timecompleted' => 'privacy:metadata:timecompleted',
  63                  'reaggregate' => 'privacy:metadata:reaggregate'
  64              ], 'privacy:metadata:coursesummary');
  65          $collection->add_database_table('course_modules_completion', [
  66                  'userid' => 'privacy:metadata:userid',
  67                  'coursemoduleid' => 'privacy:metadata:coursemoduleid',
  68                  'completionstate' => 'privacy:metadata:completionstate',
  69                  'overrideby' => 'privacy:metadata:overrideby',
  70                  'timemodified' => 'privacy:metadata:timemodified'
  71              ], 'privacy:metadata:coursemodulesummary');
  72          $collection->add_database_table('course_modules_viewed', [
  73              'userid' => 'privacy:metadata:userid',
  74              'coursemoduleid' => 'privacy:metadata:coursemoduleid',
  75              'timecreated' => 'privacy:metadata:timecreated',
  76          ], 'privacy:metadata:coursemodulesummary');
  77          $collection->add_database_table('course_completion_crit_compl', [
  78                  'userid' => 'privacy:metadata:userid',
  79                  'course' => 'privacy:metadata:course',
  80                  'gradefinal' => 'privacy:metadata:gradefinal',
  81                  'unenroled' => 'privacy:metadata:unenroled',
  82                  'timecompleted' => 'privacy:metadata:timecompleted'
  83              ], 'privacy:metadata:coursecompletedsummary');
  84          return $collection;
  85      }
  86  
  87      /**
  88       * Get join sql to retrieve courses the user is in.
  89       *
  90       * @param  int $userid The user ID
  91       * @param  string $prefix A unique prefix for these joins.
  92       * @param  string $joinfield A field to join these tables to. Joins to course ID.
  93       * @return array The join, where, and params for this join.
  94       */
  95      public static function get_course_completion_join_sql(int $userid, string $prefix, string $joinfield) : array {
  96          $cccalias = "{$prefix}_ccc"; // Course completion criteria.
  97          $cmcalias = "{$prefix}_cmc"; // Course modules completion.
  98          $cmvalias = "{$prefix}_cmv"; // Course modules viewed.
  99          $ccccalias = "{$prefix}_cccc"; // Course completion criteria completion.
 100  
 101          $join = "JOIN {course_completion_criteria} {$cccalias} ON {$joinfield} = {$cccalias}.course
 102               LEFT JOIN {course_modules_completion} {$cmcalias} ON {$cccalias}.moduleinstance = {$cmcalias}.coursemoduleid
 103                          AND {$cmcalias}.userid = :{$prefix}_moduleuserid
 104               LEFT JOIN {course_modules_viewed} {$cmvalias} ON {$cccalias}.moduleinstance = {$cmvalias}.coursemoduleid
 105                          AND {$cmvalias}.userid = :{$prefix}_moduleuserid2
 106               LEFT JOIN {course_completion_crit_compl} {$ccccalias} ON {$ccccalias}.criteriaid = {$cccalias}.id
 107                          AND {$ccccalias}.userid = :{$prefix}_courseuserid";
 108          $where = "{$cmcalias}.id IS NOT NULL OR {$ccccalias}.id IS NOT NULL OR {$cmvalias}.id IS NOT NULL";
 109          $params = ["{$prefix}_moduleuserid" => $userid, "{$prefix}_moduleuserid2" => $userid, "{$prefix}_courseuserid" => $userid];
 110          return [$join, $where, $params];
 111      }
 112  
 113      /**
 114       * Find users' course completion by context and add to the provided userlist.
 115       *
 116       * @param userlist $userlist The userlist to add to.
 117       */
 118      public static function add_course_completion_users_to_userlist(userlist $userlist) {
 119          $context = $userlist->get_context();
 120  
 121          if (!$context instanceof \context_course) {
 122              return;
 123          }
 124  
 125          $params = ['courseid' => $context->instanceid];
 126  
 127          $sql = "SELECT cmc.userid
 128                   FROM {course} c
 129                   JOIN {course_completion_criteria} ccc ON ccc.course = c.id
 130                   JOIN {course_modules_completion} cmc ON cmc.coursemoduleid = ccc.moduleinstance
 131                  WHERE c.id = :courseid";
 132  
 133          $userlist->add_from_sql('userid', $sql, $params);
 134  
 135          $sql = "SELECT cmv.userid
 136                    FROM {course} c
 137                    JOIN {course_completion_criteria} ccc ON ccc.course = c.id
 138                    JOIN {course_modules_viewed} cmv ON cmv.coursemoduleid = ccc.moduleinstance
 139                   WHERE c.id = :courseid";
 140  
 141          $userlist->add_from_sql('userid', $sql, $params);
 142  
 143          $sql = "SELECT ccc_compl.userid
 144                   FROM {course} c
 145                   JOIN {course_completion_criteria} ccc ON ccc.course = c.id
 146                   JOIN {course_completion_crit_compl} ccc_compl ON ccc_compl.criteriaid = ccc.id
 147                  WHERE c.id = :courseid";
 148  
 149          $userlist->add_from_sql('userid', $sql, $params);
 150      }
 151  
 152      /**
 153       * Returns activity completion information about a user.
 154       *
 155       * @param  \stdClass $user The user to return information about.
 156       * @param  \stdClass $course The course the user is in.
 157       * @param  \stdClass $cm Course module information.
 158       * @return \stdClass Activity completion information.
 159       */
 160      public static function get_activity_completion_info(\stdClass $user, \stdClass $course, $cm) : \stdClass {
 161          $completioninfo = new \completion_info($course);
 162          $completion = $completioninfo->is_enabled($cm);
 163          return ($completion != COMPLETION_TRACKING_NONE) ? $completioninfo->get_data($cm, true, $user->id) : new \stdClass();
 164      }
 165  
 166      /**
 167       * Returns course completion information for a user.
 168       *
 169       * @param  \stdClass $user The user that we are getting completion information for.
 170       * @param  \stdClass $course The course we are interested in.
 171       * @return \stdClass Course completion information.
 172       */
 173      public static function get_course_completion_info(\stdClass $user, \stdClass $course) : array {
 174          $completioninfo = new \completion_info($course);
 175          $completion = $completioninfo->is_enabled();
 176  
 177          if ($completion != COMPLETION_ENABLED) {
 178              return [];
 179          }
 180  
 181          $coursecomplete = $completioninfo->is_course_complete($user->id);
 182  
 183          if ($coursecomplete) {
 184              $status = get_string('complete');
 185          } else {
 186              $criteriacomplete = $completioninfo->count_course_user_data($user->id);
 187              $ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
 188  
 189              if (!$criteriacomplete && !$ccompletion->timestarted) {
 190                  $status = get_string('notyetstarted', 'completion');
 191              } else {
 192                  $status = get_string('inprogress', 'completion');
 193              }
 194          }
 195  
 196          $completions = $completioninfo->get_completions($user->id);
 197          $overall = get_string('nocriteriaset', 'completion');
 198          if (!empty($completions)) {
 199              if ($completioninfo->get_aggregation_method() == COMPLETION_AGGREGATION_ALL) {
 200                  $overall = get_string('criteriarequiredall', 'completion');
 201              } else {
 202                  $overall = get_string('criteriarequiredany', 'completion');
 203              }
 204          }
 205  
 206          $coursecompletiondata = [
 207              'status' => $status,
 208              'required' => $overall,
 209          ];
 210  
 211          $coursecompletiondata['criteria'] = array_map(function($completion) use ($completioninfo) {
 212              $criteria = $completion->get_criteria();
 213              $aggregation = $completioninfo->get_aggregation_method($criteria->criteriatype);
 214              $required = ($aggregation == COMPLETION_AGGREGATION_ALL) ? get_string('all', 'completion') :
 215                      get_string('any', 'completion');
 216              $data = [
 217                  'required' => $required,
 218                  'completed' => transform::yesno($completion->is_complete()),
 219                  'timecompleted' => isset($completion->timecompleted) ? transform::datetime($completion->timecompleted) : ''
 220              ];
 221              $details = $criteria->get_details($completion);
 222              $data = array_merge($data, $details);
 223              return $data;
 224          }, $completions);
 225          return $coursecompletiondata;
 226      }
 227  
 228      /**
 229       * Delete completion information for users.
 230       *
 231       * @param \stdClass $user The user. If provided will delete completion information for just this user. Else all users.
 232       * @param int $courseid The course id. Provide this if you want course completion and activity completion deleted.
 233       * @param int $cmid The course module id. Provide this if you only want activity completion deleted.
 234       */
 235      public static function delete_completion(\stdClass $user = null, int $courseid = null, int $cmid = null) {
 236          global $DB;
 237  
 238          if (isset($cmid)) {
 239              $params = (isset($user)) ? ['userid' => $user->id, 'coursemoduleid' => $cmid] : ['coursemoduleid' => $cmid];
 240              // Only delete the record for course modules completion.
 241              $DB->delete_records('course_modules_completion', $params);
 242              return;
 243          }
 244  
 245          if (isset($courseid)) {
 246  
 247              $usersql = isset($user) ? 'AND cmc.userid = :userid' : '';
 248              $usercmvsql = isset($user) ? 'AND cmv.userid = :userid' : '';
 249              $params = isset($user) ? ['course' => $courseid, 'userid' => $user->id] : ['course' => $courseid];
 250  
 251              // Find records relating to course modules.
 252              $sql = "SELECT cmc.id
 253                        FROM {course_completion_criteria} ccc
 254                        JOIN {course_modules_completion} cmc ON ccc.moduleinstance = cmc.coursemoduleid
 255                       WHERE ccc.course = :course $usersql";
 256              $recordids = $DB->get_records_sql($sql, $params);
 257              $ids = array_keys($recordids);
 258              if (!empty($ids)) {
 259                  list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
 260                  $deletesql = 'id ' . $deletesql;
 261                  $DB->delete_records_select('course_modules_completion', $deletesql, $deleteparams);
 262              }
 263              // Find records relating to course modules completion viewed.
 264              $sql = "SELECT cmv.id
 265                        FROM {course_completion_criteria} ccc
 266                        JOIN {course_modules_viewed} cmv ON ccc.moduleinstance = cmv.coursemoduleid
 267                       WHERE ccc.course = :course $usercmvsql";
 268              $recordids = $DB->get_records_sql($sql, $params);
 269              $ids = array_keys($recordids);
 270              if (!empty($ids)) {
 271                  list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
 272                  $deletesql = 'id ' . $deletesql;
 273                  $DB->delete_records_select('course_modules_viewed', $deletesql, $deleteparams);
 274              }
 275  
 276              $DB->delete_records('course_completion_crit_compl', $params);
 277              $DB->delete_records('course_completions', $params);
 278          }
 279      }
 280  
 281      /**
 282       * Delete completion information for users within an approved userlist.
 283       *
 284       * @param approved_userlist $userlist The approved userlist of users to delete completion information for.
 285       * @param int $courseid The course id. Provide this if you want course completion and activity completion deleted.
 286       * @param int $cmid The course module id. Provide this if you only want activity completion deleted.
 287       */
 288      public static function delete_completion_by_approved_userlist(approved_userlist $userlist, int $courseid = null, int $cmid = null) {
 289          global $DB;
 290          $userids = $userlist->get_userids();
 291  
 292          if (empty($userids)) {
 293              return;
 294          }
 295  
 296          list($useridsql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
 297  
 298          if (isset($cmid)) {
 299              $params['coursemoduleid'] = $cmid;
 300  
 301              // Only delete the record for course modules completion.
 302              $sql = "coursemoduleid = :coursemoduleid AND userid {$useridsql}";
 303              $DB->delete_records_select('course_modules_completion', $sql, $params);
 304              $DB->delete_records_select('course_modules_viewed', $sql, $params);
 305              return;
 306          }
 307  
 308          if (isset($courseid)) {
 309              $params['course'] = $courseid;
 310  
 311              // Find records relating to course modules.
 312              $sql = "SELECT cmc.id
 313                        FROM {course_completion_criteria} ccc
 314                        JOIN {course_modules_completion} cmc ON ccc.moduleinstance = cmc.coursemoduleid
 315                       WHERE ccc.course = :course AND cmc.userid {$useridsql}";
 316              $recordids = $DB->get_records_sql($sql, $params);
 317              $ids = array_keys($recordids);
 318              if (!empty($ids)) {
 319                  list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
 320                  $deletesql = 'id ' . $deletesql;
 321                  $DB->delete_records_select('course_modules_completion', $deletesql, $deleteparams);
 322              }
 323  
 324              // Find records relating to course modules.
 325              $sql = "SELECT cmv.id
 326                        FROM {course_completion_criteria} ccc
 327                        JOIN {course_modules_viewed} cmv ON ccc.moduleinstance = cmv.coursemoduleid
 328                       WHERE ccc.course = :course AND cmv.userid {$useridsql}";
 329              $recordids = $DB->get_records_sql($sql, $params);
 330              $ids = array_keys($recordids);
 331              if (!empty($ids)) {
 332                  list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
 333                  $deletesql = 'id ' . $deletesql;
 334                  $DB->delete_records_select('course_modules_viewed', $deletesql, $deleteparams);
 335              }
 336  
 337              $sql = "course = :course AND userid {$useridsql}";
 338              $DB->delete_records_select('course_completion_crit_compl', $sql, $params);
 339              $DB->delete_records_select('course_completions', $sql, $params);
 340          }
 341      }
 342  }