Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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   * 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                  'viewed' => 'privacy:metadata:viewed',
  70                  'overrideby' => 'privacy:metadata:overrideby',
  71                  'timemodified' => 'privacy:metadata:timemodified'
  72              ], 'privacy:metadata:coursemodulesummary');
  73          $collection->add_database_table('course_completion_crit_compl', [
  74                  'userid' => 'privacy:metadata:userid',
  75                  'course' => 'privacy:metadata:course',
  76                  'gradefinal' => 'privacy:metadata:gradefinal',
  77                  'unenroled' => 'privacy:metadata:unenroled',
  78                  'timecompleted' => 'privacy:metadata:timecompleted'
  79              ], 'privacy:metadata:coursecompletedsummary');
  80          return $collection;
  81      }
  82  
  83      /**
  84       * Get join sql to retrieve courses the user is in.
  85       *
  86       * @param  int $userid The user ID
  87       * @param  string $prefix A unique prefix for these joins.
  88       * @param  string $joinfield A field to join these tables to. Joins to course ID.
  89       * @return array The join, where, and params for this join.
  90       */
  91      public static function get_course_completion_join_sql(int $userid, string $prefix, string $joinfield) : array {
  92          $cccalias = "{$prefix}_ccc"; // Course completion criteria.
  93          $cmcalias = "{$prefix}_cmc"; // Course modules completion.
  94          $ccccalias = "{$prefix}_cccc"; // Course completion criteria completion.
  95  
  96          $join = "JOIN {course_completion_criteria} {$cccalias} ON {$joinfield} = {$cccalias}.course
  97               LEFT JOIN {course_modules_completion} {$cmcalias} ON {$cccalias}.moduleinstance = {$cmcalias}.coursemoduleid
  98                          AND {$cmcalias}.userid = :{$prefix}_moduleuserid
  99               LEFT JOIN {course_completion_crit_compl} {$ccccalias} ON {$ccccalias}.criteriaid = {$cccalias}.id
 100                          AND {$ccccalias}.userid = :{$prefix}_courseuserid";
 101          $where = "{$cmcalias}.id IS NOT NULL OR {$ccccalias}.id IS NOT NULL";
 102          $params = ["{$prefix}_moduleuserid" => $userid, "{$prefix}_courseuserid" => $userid];
 103  
 104          return [$join, $where, $params];
 105      }
 106  
 107      /**
 108       * Find users' course completion by context and add to the provided userlist.
 109       *
 110       * @param userlist $userlist The userlist to add to.
 111       */
 112      public static function add_course_completion_users_to_userlist(userlist $userlist) {
 113          $context = $userlist->get_context();
 114  
 115          if (!$context instanceof \context_course) {
 116              return;
 117          }
 118  
 119          $params = ['courseid' => $context->instanceid];
 120  
 121          $sql = "SELECT cmc.userid
 122                   FROM {course} c
 123                   JOIN {course_completion_criteria} ccc ON ccc.course = c.id
 124                   JOIN {course_modules_completion} cmc ON cmc.coursemoduleid = ccc.moduleinstance
 125                  WHERE c.id = :courseid";
 126  
 127          $userlist->add_from_sql('userid', $sql, $params);
 128  
 129          $sql = "SELECT ccc_compl.userid
 130                   FROM {course} c
 131                   JOIN {course_completion_criteria} ccc ON ccc.course = c.id
 132                   JOIN {course_completion_crit_compl} ccc_compl ON ccc_compl.criteriaid = ccc.id
 133                  WHERE c.id = :courseid";
 134  
 135          $userlist->add_from_sql('userid', $sql, $params);
 136      }
 137  
 138      /**
 139       * Returns activity completion information about a user.
 140       *
 141       * @param  \stdClass $user The user to return information about.
 142       * @param  \stdClass $course The course the user is in.
 143       * @param  \stdClass $cm Course module information.
 144       * @return \stdClass Activity completion information.
 145       */
 146      public static function get_activity_completion_info(\stdClass $user, \stdClass $course, $cm) : \stdClass {
 147          $completioninfo = new \completion_info($course);
 148          $completion = $completioninfo->is_enabled($cm);
 149          return ($completion != COMPLETION_TRACKING_NONE) ? $completioninfo->get_data($cm, true, $user->id) : new \stdClass();
 150      }
 151  
 152      /**
 153       * Returns course completion information for a user.
 154       *
 155       * @param  \stdClass $user The user that we are getting completion information for.
 156       * @param  \stdClass $course The course we are interested in.
 157       * @return \stdClass Course completion information.
 158       */
 159      public static function get_course_completion_info(\stdClass $user, \stdClass $course) : array {
 160          $completioninfo = new \completion_info($course);
 161          $completion = $completioninfo->is_enabled();
 162  
 163          if ($completion != COMPLETION_ENABLED) {
 164              return [];
 165          }
 166  
 167          $coursecomplete = $completioninfo->is_course_complete($user->id);
 168  
 169          if ($coursecomplete) {
 170              $status = get_string('complete');
 171          } else {
 172              $criteriacomplete = $completioninfo->count_course_user_data($user->id);
 173              $ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
 174  
 175              if (!$criteriacomplete && !$ccompletion->timestarted) {
 176                  $status = get_string('notyetstarted', 'completion');
 177              } else {
 178                  $status = get_string('inprogress', 'completion');
 179              }
 180          }
 181  
 182          $completions = $completioninfo->get_completions($user->id);
 183          $overall = get_string('nocriteriaset', 'completion');
 184          if (!empty($completions)) {
 185              if ($completioninfo->get_aggregation_method() == COMPLETION_AGGREGATION_ALL) {
 186                  $overall = get_string('criteriarequiredall', 'completion');
 187              } else {
 188                  $overall = get_string('criteriarequiredany', 'completion');
 189              }
 190          }
 191  
 192          $coursecompletiondata = [
 193              'status' => $status,
 194              'required' => $overall,
 195          ];
 196  
 197          $coursecompletiondata['criteria'] = array_map(function($completion) use ($completioninfo) {
 198              $criteria = $completion->get_criteria();
 199              $aggregation = $completioninfo->get_aggregation_method($criteria->criteriatype);
 200              $required = ($aggregation == COMPLETION_AGGREGATION_ALL) ? get_string('all', 'completion') :
 201                      get_string('any', 'completion');
 202              $data = [
 203                  'required' => $required,
 204                  'completed' => transform::yesno($completion->is_complete()),
 205                  'timecompleted' => isset($completion->timecompleted) ? transform::datetime($completion->timecompleted) : ''
 206              ];
 207              $details = $criteria->get_details($completion);
 208              $data = array_merge($data, $details);
 209              return $data;
 210          }, $completions);
 211          return $coursecompletiondata;
 212      }
 213  
 214      /**
 215       * Delete completion information for users.
 216       *
 217       * @param \stdClass $user The user. If provided will delete completion information for just this user. Else all users.
 218       * @param int $courseid The course id. Provide this if you want course completion and activity completion deleted.
 219       * @param int $cmid The course module id. Provide this if you only want activity completion deleted.
 220       */
 221      public static function delete_completion(\stdClass $user = null, int $courseid = null, int $cmid = null) {
 222          global $DB;
 223  
 224          if (isset($cmid)) {
 225              $params = (isset($user)) ? ['userid' => $user->id, 'coursemoduleid' => $cmid] : ['coursemoduleid' => $cmid];
 226              // Only delete the record for course modules completion.
 227              $DB->delete_records('course_modules_completion', $params);
 228              return;
 229          }
 230  
 231          if (isset($courseid)) {
 232  
 233              $usersql = isset($user) ? 'AND cmc.userid = :userid' : '';
 234              $params = isset($user) ? ['course' => $courseid, 'userid' => $user->id] : ['course' => $courseid];
 235  
 236              // Find records relating to course modules.
 237              $sql = "SELECT cmc.id
 238                        FROM {course_completion_criteria} ccc
 239                        JOIN {course_modules_completion} cmc ON ccc.moduleinstance = cmc.coursemoduleid
 240                       WHERE ccc.course = :course $usersql";
 241              $recordids = $DB->get_records_sql($sql, $params);
 242              $ids = array_keys($recordids);
 243              if (!empty($ids)) {
 244                  list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
 245                  $deletesql = 'id ' . $deletesql;
 246                  $DB->delete_records_select('course_modules_completion', $deletesql, $deleteparams);
 247              }
 248              $DB->delete_records('course_completion_crit_compl', $params);
 249              $DB->delete_records('course_completions', $params);
 250          }
 251      }
 252  
 253      /**
 254       * Delete completion information for users within an approved userlist.
 255       *
 256       * @param approved_userlist $userlist The approved userlist of users to delete completion information for.
 257       * @param int $courseid The course id. Provide this if you want course completion and activity completion deleted.
 258       * @param int $cmid The course module id. Provide this if you only want activity completion deleted.
 259       */
 260      public static function delete_completion_by_approved_userlist(approved_userlist $userlist, int $courseid = null, int $cmid = null) {
 261          global $DB;
 262          $userids = $userlist->get_userids();
 263  
 264          if (empty($userids)) {
 265              return;
 266          }
 267  
 268          list($useridsql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
 269  
 270          if (isset($cmid)) {
 271              $params['coursemoduleid'] = $cmid;
 272  
 273              // Only delete the record for course modules completion.
 274              $sql = "coursemoduleid = :coursemoduleid AND userid {$useridsql}";
 275              $DB->delete_records_select('course_modules_completion', $sql, $params);
 276              return;
 277          }
 278  
 279          if (isset($courseid)) {
 280              $params['course'] = $courseid;
 281  
 282              // Find records relating to course modules.
 283              $sql = "SELECT cmc.id
 284                        FROM {course_completion_criteria} ccc
 285                        JOIN {course_modules_completion} cmc ON ccc.moduleinstance = cmc.coursemoduleid
 286                       WHERE ccc.course = :course AND cmc.userid {$useridsql}";
 287              $recordids = $DB->get_records_sql($sql, $params);
 288              $ids = array_keys($recordids);
 289              if (!empty($ids)) {
 290                  list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
 291                  $deletesql = 'id ' . $deletesql;
 292                  $DB->delete_records_select('course_modules_completion', $deletesql, $deleteparams);
 293              }
 294  
 295              $sql = "course = :course AND userid {$useridsql}";
 296              $DB->delete_records_select('course_completion_crit_compl', $sql, $params);
 297              $DB->delete_records_select('course_completions', $sql, $params);
 298          }
 299      }
 300  }