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 311 and 401] [Versions 401 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          } else {
 156              if (function_exists($this->cminfo->modname . '_get_completion_state')) {
 157                  // If the plugin does not have the custom completion implementation but implements the get_completion_state() callback,
 158                  // fallback to displaying the overall completion state of the activity.
 159                  $details = [
 160                      'plugincompletionstate' => (object)[
 161                          'status' => $this->get_overall_completion(),
 162                          'description' => get_string('completeactivity', 'completion')
 163                      ]
 164                  ];
 165              }
 166          }
 167  
 168          return $details;
 169      }
 170  
 171      /**
 172       * Sort completion details in the order specified by the activity's custom completion implementation.
 173       *
 174       * @param array $details The completion details to be sorted.
 175       * @return array
 176       * @throws \coding_exception
 177       */
 178      protected function sort_completion_details(array $details): array {
 179          $sortorder = $this->cmcompletion->get_sort_order();
 180          $sorteddetails = [];
 181  
 182          foreach ($sortorder as $sortedkey) {
 183              if (isset($details[$sortedkey])) {
 184                  $sorteddetails[$sortedkey] = $details[$sortedkey];
 185              }
 186          }
 187  
 188          // Make sure the sorted list includes all of the conditions that were set.
 189          if (count($sorteddetails) < count($details)) {
 190              $exceptiontext = get_class($this->cmcompletion) .'::get_sort_order() is missing one or more completion conditions.' .
 191                  ' All custom and standard conditions that apply to this activity must be listed.';
 192              throw new \coding_exception($exceptiontext);
 193          }
 194  
 195          return $sorteddetails;
 196      }
 197  
 198      /**
 199       * Fetches the overall completion state of this course module.
 200       *
 201       * @return int The overall completion state for this course module.
 202       */
 203      public function get_overall_completion(): int {
 204          return (int)$this->completiondata->completionstate;
 205      }
 206  
 207      /**
 208       * Whether this activity module has completion enabled.
 209       *
 210       * @return bool
 211       */
 212      public function has_completion(): bool {
 213          return $this->completioninfo->is_enabled($this->cminfo) != COMPLETION_DISABLED;
 214      }
 215  
 216      /**
 217       * Whether this activity module instance tracks completion automatically.
 218       *
 219       * @return bool
 220       */
 221      public function is_automatic(): bool {
 222          return $this->cminfo->completion == COMPLETION_TRACKING_AUTOMATIC;
 223      }
 224  
 225      /**
 226       * Fetches the user ID that has overridden the completion state of this activity for the user.
 227       *
 228       * @return int|null
 229       */
 230      public function overridden_by(): ?int {
 231          return isset($this->completiondata->overrideby) ? (int)$this->completiondata->overrideby : null;
 232      }
 233  
 234      /**
 235       * Checks whether completion is being tracked for this user.
 236       *
 237       * @return bool
 238       */
 239      public function is_tracked_user(): bool {
 240          return $this->completioninfo->is_tracked_user($this->userid);
 241      }
 242  
 243      /**
 244       * Determine whether to show the manual completion or not.
 245       *
 246       * @return bool
 247       */
 248      public function show_manual_completion(): bool {
 249          global $PAGE;
 250  
 251          if ($PAGE->context->contextlevel == CONTEXT_MODULE) {
 252              // Manual completion should always be shown on the activity page.
 253              return true;
 254          } else {
 255              $course = $this->cminfo->get_course();
 256              if ($course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS) {
 257                  return true;
 258              } else if ($this->cmcompletion) {
 259                  return $this->cmcompletion->manual_completion_always_shown();
 260              }
 261          }
 262  
 263          return false;
 264      }
 265  
 266      /**
 267       * Completion state timemodified
 268       *
 269       * @return int timestamp
 270       */
 271      public function get_timemodified(): int {
 272          return (int)$this->completiondata->timemodified;
 273      }
 274  
 275      /**
 276       * Generates an instance of this class.
 277       *
 278       * @param cm_info $cminfo The course module info instance.
 279       * @param int $userid The user ID that we're fetching completion details for.
 280       * @param bool $returndetails  Whether to return completion details or not.
 281       * @return cm_completion_details
 282       */
 283      public static function get_instance(cm_info $cminfo, int $userid, bool $returndetails = true): cm_completion_details {
 284          $course = $cminfo->get_course();
 285          $completioninfo = new \completion_info($course);
 286          return new self($completioninfo, $cminfo, $userid, $returndetails);
 287      }
 288  }