Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 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($this->cminfo, $this->userid);
  77          }
  78      }
  79  
  80      /**
  81       * Fetches the completion details for a user.
  82       *
  83       * @return array An array of completion details for a user containing the completion requirement's description and status.
  84       * @throws \coding_exception
  85       */
  86      public function get_details(): array {
  87          if (!$this->is_automatic()) {
  88              // No details need to be returned for modules that don't have automatic completion tracking enabled.
  89              return [];
  90          }
  91  
  92          if (!$this->returndetails) {
  93              // We don't need to return completion details.
  94              return [];
  95          }
  96  
  97          $completiondata = $this->completiondata;
  98          $hasoverride = !empty($this->overridden_by());
  99  
 100          $details = [];
 101  
 102          // Completion rule: Student must view this activity.
 103          if (!empty($this->cminfo->completionview)) {
 104              if (!$hasoverride) {
 105                  $status = COMPLETION_INCOMPLETE;
 106                  if ($completiondata->viewed == COMPLETION_VIEWED) {
 107                      $status = COMPLETION_COMPLETE;
 108                  }
 109              } else {
 110                  $status = $completiondata->completionstate;
 111              }
 112  
 113              $details['completionview'] = (object)[
 114                  'status' => $status,
 115                  'description' => get_string('detail_desc:view', 'completion'),
 116              ];
 117          }
 118  
 119          // Completion rule: Student must receive a grade.
 120          if (!is_null($this->cminfo->completiongradeitemnumber)) {
 121              if (!$hasoverride) {
 122                  $status = $completiondata->completiongrade ?? COMPLETION_INCOMPLETE;
 123              } else {
 124                  $status = $completiondata->completionstate;
 125              }
 126  
 127              $details['completionusegrade'] = (object)[
 128                  'status' => $status,
 129                  'description' => get_string('detail_desc:receivegrade', 'completion'),
 130              ];
 131          }
 132  
 133          if ($this->cmcompletion) {
 134              if (isset($completiondata->customcompletion)) {
 135                  foreach ($completiondata->customcompletion as $rule => $status) {
 136                      $details[$rule] = (object)[
 137                          'status' => !$hasoverride ? $status : $completiondata->completionstate,
 138                          'description' => $this->cmcompletion->get_custom_rule_description($rule),
 139                      ];
 140                  }
 141  
 142                  $details = $this->sort_completion_details($details);
 143              }
 144          } else {
 145              if (function_exists($this->cminfo->modname . '_get_completion_state')) {
 146                  // If the plugin does not have the custom completion implementation but implements the get_completion_state() callback,
 147                  // fallback to displaying the overall completion state of the activity.
 148                  $details = [
 149                      'plugincompletionstate' => (object)[
 150                          'status' => $this->get_overall_completion(),
 151                          'description' => get_string('completeactivity', 'completion')
 152                      ]
 153                  ];
 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       * Whether this activity module has completion enabled.
 198       *
 199       * @return bool
 200       */
 201      public function has_completion(): bool {
 202          return $this->completioninfo->is_enabled($this->cminfo) != COMPLETION_DISABLED;
 203      }
 204  
 205      /**
 206       * Whether this activity module instance tracks completion automatically.
 207       *
 208       * @return bool
 209       */
 210      public function is_automatic(): bool {
 211          return $this->cminfo->completion == COMPLETION_TRACKING_AUTOMATIC;
 212      }
 213  
 214      /**
 215       * Fetches the user ID that has overridden the completion state of this activity for the user.
 216       *
 217       * @return int|null
 218       */
 219      public function overridden_by(): ?int {
 220          return isset($this->completiondata->overrideby) ? (int)$this->completiondata->overrideby : null;
 221      }
 222  
 223      /**
 224       * Checks whether completion is being tracked for this user.
 225       *
 226       * @return bool
 227       */
 228      public function is_tracked_user(): bool {
 229          return $this->completioninfo->is_tracked_user($this->userid);
 230      }
 231  
 232      /**
 233       * Determine whether to show the manual completion or not.
 234       *
 235       * @return bool
 236       */
 237      public function show_manual_completion(): bool {
 238          global $PAGE;
 239  
 240          if ($PAGE->context->contextlevel == CONTEXT_MODULE) {
 241              // Manual completion should always be shown on the activity page.
 242              return true;
 243          } else {
 244              $course = $this->cminfo->get_course();
 245              if ($course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS) {
 246                  return true;
 247              } else if ($this->cmcompletion) {
 248                  return $this->cmcompletion->manual_completion_always_shown();
 249              }
 250          }
 251  
 252          return false;
 253      }
 254  
 255      /**
 256       * Completion state timemodified
 257       *
 258       * @return int timestamp
 259       */
 260      public function get_timemodified(): int {
 261          return (int)$this->completiondata->timemodified;
 262      }
 263  
 264      /**
 265       * Generates an instance of this class.
 266       *
 267       * @param cm_info $cminfo The course module info instance.
 268       * @param int $userid The user ID that we're fetching completion details for.
 269       * @param bool $returndetails  Whether to return completion details or not.
 270       * @return cm_completion_details
 271       */
 272      public static function get_instance(cm_info $cminfo, int $userid, bool $returndetails = true): cm_completion_details {
 273          $course = $cminfo->get_course();
 274          $completioninfo = new \completion_info($course);
 275          return new self($completioninfo, $cminfo, $userid, $returndetails);
 276      }
 277  }