Search moodle.org's
Developer Documentation

See Release Notes

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

Differences Between: [Versions 311 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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   * Contains the class for building the user's activity completion details.
  19   *
  20   * @package   core_completion
  21   * @copyright 2021 Jun Pataleta <jun@moodle.com>
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  declare(strict_types = 1);
  26  
  27  namespace core_completion;
  28  
  29  use cm_info;
  30  use completion_info;
  31  
  32  /**
  33   * Class for building the user's activity completion details.
  34   *
  35   * @package   core_completion
  36   * @copyright 2021 Jun Pataleta <jun@moodle.com>
  37   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class cm_completion_details {
  40      /** @var completion_info The completion info instance for this cm's course. */
  41      protected $completioninfo = null;
  42  
  43      /** @var object The completion data. */
  44      protected $completiondata = null;
  45  
  46      /** @var cm_info The course module information. */
  47      protected $cminfo = null;
  48  
  49      /** @var int The user ID. */
  50      protected $userid = 0;
  51  
  52      /** @var bool Whether to return automatic completion details. */
  53      protected $returndetails = true;
  54  
  55      /** @var activity_custom_completion Activity custom completion object. */
  56      protected $cmcompletion = null;
  57  
  58      /**
  59       * Constructor.
  60       *
  61       * @param completion_info $completioninfo The completion info instance for this cm's course.
  62       * @param cm_info $cminfo The course module information.
  63       * @param int $userid The user ID.
  64       * @param bool $returndetails Whether to return completion details or not.
  65       */
  66      public function __construct(completion_info $completioninfo, cm_info $cminfo, int $userid, bool $returndetails = true) {
  67          $this->completioninfo = $completioninfo;
  68          // We need to pass wholecourse = true here for better performance. All the course's completion data for the current
  69          // logged-in user will get in a single query instead of multiple queries and loaded to cache.
  70          $this->completiondata = $completioninfo->get_data($cminfo, true, $userid);
  71          $this->cminfo = $cminfo;
  72          $this->userid = $userid;
  73          $this->returndetails = $returndetails;
  74          $cmcompletionclass = activity_custom_completion::get_cm_completion_class($this->cminfo->modname);
  75          if ($cmcompletionclass) {
  76              $this->cmcompletion = new $cmcompletionclass(
  77                  $this->cminfo,
  78                  $this->userid,
  79                  $completioninfo->get_core_completion_state($cminfo, $userid)
  80              );
  81          }
  82      }
  83  
  84      /**
  85       * Fetches the completion details for a user.
  86       *
  87       * @return array An array of completion details for a user containing the completion requirement's description and status.
  88       * @throws \coding_exception
  89       */
  90      public function get_details(): array {
  91          if (!$this->is_automatic()) {
  92              // No details need to be returned for modules that don't have automatic completion tracking enabled.
  93              return [];
  94          }
  95  
  96          if (!$this->returndetails) {
  97              // We don't need to return completion details.
  98              return [];
  99          }
 100  
 101          $completiondata = $this->completiondata;
 102          $hasoverride = !empty($this->overridden_by());
 103  
 104          $details = [];
 105  
 106          // Completion rule: Student must view this activity.
 107          if (!empty($this->cminfo->completionview)) {
 108              if (!$hasoverride) {
 109                  $status = COMPLETION_INCOMPLETE;
 110                  if ($completiondata->viewed == COMPLETION_VIEWED) {
 111                      $status = COMPLETION_COMPLETE;
 112                  }
 113              } else {
 114                  $status = $completiondata->completionstate;
 115              }
 116  
 117              $details['completionview'] = (object)[
 118                  'status' => $status,
 119                  'description' => get_string('detail_desc:view', 'completion'),
 120              ];
 121          }
 122  
 123          // Completion rule: Student must receive a grade.
 124          if (!is_null($this->cminfo->completiongradeitemnumber)) {
 125              if (!$hasoverride) {
 126                  $status = $completiondata->completiongrade ?? COMPLETION_INCOMPLETE;
 127              } else {
 128                  $status = $completiondata->completionstate;
 129              }
 130  
 131              $details['completionusegrade'] = (object)[
 132                  'status' => $status,
 133                  'description' => get_string('detail_desc:receivegrade', 'completion'),
 134              ];
 135  
 136              if (!is_null($this->cminfo->completionpassgrade) && $this->cminfo->completionpassgrade) {
 137                  $details['completionpassgrade'] = (object)[
 138                      'status' => $completiondata->passgrade ?? COMPLETION_INCOMPLETE,
 139                      'description' => get_string('detail_desc:receivepassgrade', 'completion'),
 140                  ];
 141              }
 142          }
 143  
 144          if ($this->cmcompletion) {
 145              if (isset($completiondata->customcompletion)) {
 146                  foreach ($completiondata->customcompletion as $rule => $status) {
 147                      $details[$rule] = (object)[
 148                          'status' => !$hasoverride ? $status : $completiondata->completionstate,
 149                          'description' => $this->cmcompletion->get_custom_rule_description($rule),
 150                      ];
 151                  }
 152  
 153                  $details = $this->sort_completion_details($details);
 154              }
 155          }
 156  
 157          return $details;
 158      }
 159  
 160      /**
 161       * Sort completion details in the order specified by the activity's custom completion implementation.
 162       *
 163       * @param array $details The completion details to be sorted.
 164       * @return array
 165       * @throws \coding_exception
 166       */
 167      protected function sort_completion_details(array $details): array {
 168          $sortorder = $this->cmcompletion->get_sort_order();
 169          $sorteddetails = [];
 170  
 171          foreach ($sortorder as $sortedkey) {
 172              if (isset($details[$sortedkey])) {
 173                  $sorteddetails[$sortedkey] = $details[$sortedkey];
 174              }
 175          }
 176  
 177          // Make sure the sorted list includes all of the conditions that were set.
 178          if (count($sorteddetails) < count($details)) {
 179              $exceptiontext = get_class($this->cmcompletion) .'::get_sort_order() is missing one or more completion conditions.' .
 180                  ' All custom and standard conditions that apply to this activity must be listed.';
 181              throw new \coding_exception($exceptiontext);
 182          }
 183  
 184          return $sorteddetails;
 185      }
 186  
 187      /**
 188       * Fetches the overall completion state of this course module.
 189       *
 190       * @return int The overall completion state for this course module.
 191       */
 192      public function get_overall_completion(): int {
 193          return (int)$this->completiondata->completionstate;
 194      }
 195  
 196      /**
 197       * Returns whether the overall completion state of this course module should be marked as complete or not.
 198       * This is based on the completion settings of the course module, so when the course module requires a passing grade,
 199       * it will only be marked as complete when the user has passed the course module. Otherwise, it will be marked as complete
 200       * even when the user has failed the course module.
 201       *
 202       * @return bool True when the module can be marked as completed.
 203       */
 204      public function is_overall_complete(): bool {
 205          $completionstates = [];
 206          if ($this->is_manual()) {
 207              $completionstates = [COMPLETION_COMPLETE];
 208          } else if ($this->is_automatic()) {
 209              // Successfull completion states depend on the completion settings.
 210              if (isset($this->completiondata->passgrade)) {
 211                  // Passing grade is required. Don't mark it as complete when state is COMPLETION_COMPLETE_FAIL.
 212                  $completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS];
 213              } else {
 214                  // Any grade is required. Mark it as complete even when state is COMPLETION_COMPLETE_FAIL.
 215                  $completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL];
 216              }
 217          }
 218  
 219          return in_array($this->get_overall_completion(), $completionstates);
 220      }
 221  
 222      /**
 223       * Whether this activity module has completion enabled.
 224       *
 225       * @return bool
 226       */
 227      public function has_completion(): bool {
 228          return $this->completioninfo->is_enabled($this->cminfo) != COMPLETION_DISABLED;
 229      }
 230  
 231      /**
 232       * Whether this activity module instance tracks completion automatically.
 233       *
 234       * @return bool
 235       */
 236      public function is_automatic(): bool {
 237          return $this->cminfo->completion == COMPLETION_TRACKING_AUTOMATIC;
 238      }
 239  
 240      /**
 241       * Whether this activity module instance tracks completion manually.
 242       *
 243       * @return bool
 244       */
 245      public function is_manual(): bool {
 246          return $this->cminfo->completion == COMPLETION_TRACKING_MANUAL;
 247      }
 248  
 249      /**
 250       * Fetches the user ID that has overridden the completion state of this activity for the user.
 251       *
 252       * @return int|null
 253       */
 254      public function overridden_by(): ?int {
 255          return isset($this->completiondata->overrideby) ? (int)$this->completiondata->overrideby : null;
 256      }
 257  
 258      /**
 259       * Checks whether completion is being tracked for this user.
 260       *
 261       * @return bool
 262       */
 263      public function is_tracked_user(): bool {
 264          return $this->completioninfo->is_tracked_user($this->userid);
 265      }
 266  
 267      /**
 268       * Determine whether to show the manual completion or not.
 269       *
 270       * @return bool
 271       */
 272      public function show_manual_completion(): bool {
 273          global $PAGE;
 274  
 275          if (!$this->is_manual()) {
 276              return false;
 277          }
 278  
 279          if ($PAGE->context->contextlevel == CONTEXT_MODULE) {
 280              // Manual completion should always be shown on the activity page.
 281              return true;
 282          } else {
 283              $course = $this->cminfo->get_course();
 284              if ($course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS) {
 285                  return true;
 286              } else if ($this->cmcompletion) {
 287                  return $this->cmcompletion->manual_completion_always_shown();
 288              }
 289          }
 290  
 291          return false;
 292      }
 293  
 294      /**
 295       * Completion state timemodified
 296       *
 297       * @return int timestamp
 298       */
 299      public function get_timemodified(): int {
 300          return (int)$this->completiondata->timemodified;
 301      }
 302  
 303      /**
 304       * Generates an instance of this class.
 305       *
 306       * @param cm_info $cminfo The course module info instance.
 307       * @param int $userid The user ID that we're fetching completion details for.
 308       * @param bool $returndetails  Whether to return completion details or not.
 309       * @return cm_completion_details
 310       */
 311      public static function get_instance(cm_info $cminfo, int $userid, bool $returndetails = true): cm_completion_details {
 312          $course = $cminfo->get_course();
 313          $completioninfo = new \completion_info($course);
 314          return new self($completioninfo, $cminfo, $userid, $returndetails);
 315      }
 316  }