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 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   * Base form for changing completion rules
  19   *
  20   * @package     core_completion
  21   * @copyright   2017 Marina Glancy
  22   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die;
  26  
  27  require_once($CFG->libdir.'/formslib.php');
  28  require_once($CFG->libdir.'/completionlib.php');
  29  require_once($CFG->dirroot.'/course/modlib.php');
  30  
  31  /**
  32   * Base form for changing completion rules. Used in bulk editing activity completion and editing default activity completion
  33   *
  34   * @package     core_completion
  35   * @copyright   2017 Marina Glancy
  36   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  abstract class core_completion_edit_base_form extends moodleform {
  39      /** @var moodleform_mod Do not use directly, call $this->get_module_form() */
  40      protected $_moduleform = null;
  41      /** @var bool */
  42      protected $hascustomrules = false;
  43      /** @var stdClass */
  44      protected $course;
  45  
  46      /**
  47       * Returns list of types of selected module types
  48       *
  49       * @return array modname=>modfullname
  50       */
  51      abstract protected function get_module_names();
  52  
  53      /**
  54       * Returns true if all selected modules support tracking view.
  55       *
  56       * @return bool
  57       */
  58      protected function support_views() {
  59          foreach ($this->get_module_names() as $modname => $modfullname) {
  60              if (!plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false)) {
  61                  return false;
  62              }
  63          }
  64          return true;
  65      }
  66  
  67      /**
  68       * Returns true if all selected modules support grading.
  69       *
  70       * @return bool
  71       */
  72      protected function support_grades() {
  73          foreach ($this->get_module_names() as $modname => $modfullname) {
  74              if (!plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false)) {
  75                  return false;
  76              }
  77          }
  78          return true;
  79      }
  80  
  81      /**
  82       * Returns an instance of component-specific module form for the first selected module
  83       *
  84       * @return moodleform_mod|null
  85       */
  86      abstract protected function get_module_form();
  87  
  88      /**
  89       * If all selected modules are of the same module type, adds custom completion rules from this module type
  90       *
  91       * @return array
  92       */
  93      protected function add_custom_completion_rules() {
  94          $modnames = array_keys($this->get_module_names());
  95          if (count($modnames) != 1 || !plugin_supports('mod', $modnames[0], FEATURE_COMPLETION_HAS_RULES, false)) {
  96              return [];
  97          }
  98  
  99          try {
 100              // Add completion rules from the module form to this form.
 101              $moduleform = $this->get_module_form();
 102              $moduleform->_form = $this->_form;
 103              if ($customcompletionelements = $moduleform->add_completion_rules()) {
 104                  $this->hascustomrules = true;
 105              }
 106              return $customcompletionelements;
 107          } catch (Exception $e) {
 108              debugging('Could not add custom completion rule of module ' . $modnames[0] .
 109                  ' to this form, this has to be fixed by the developer', DEBUG_DEVELOPER);
 110              return [];
 111          }
 112      }
 113  
 114      /**
 115       * Checks if at least one of the custom completion rules is enabled
 116       *
 117       * @param array $data Input data (not yet validated)
 118       * @return bool True if one or more rules is enabled, false if none are;
 119       *   default returns false
 120       */
 121      protected function completion_rule_enabled($data) {
 122          if ($this->hascustomrules) {
 123              return $this->get_module_form()->completion_rule_enabled($data);
 124          }
 125          return false;
 126      }
 127  
 128      /**
 129       * Returns list of modules that have automatic completion rules that are not shown on this form
 130       * (because they are not present in at least one other selected module).
 131       *
 132       * @return array
 133       */
 134      protected function get_modules_with_hidden_rules() {
 135          $modnames = $this->get_module_names();
 136          if (count($modnames) <= 1) {
 137              // No rules definitions conflicts if there is only one module type.
 138              return [];
 139          }
 140  
 141          $conflicts = [];
 142  
 143          if (!$this->support_views()) {
 144              // If we don't display views rule but at least one module supports it - we have conflicts.
 145              foreach ($modnames as $modname => $modfullname) {
 146                  if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false)) {
 147                      $conflicts[$modname] = $modfullname;
 148                  }
 149              }
 150          }
 151  
 152          if (!$this->support_grades()) {
 153              // If we don't display grade rule but at least one module supports it - we have conflicts.
 154              foreach ($modnames as $modname => $modfullname) {
 155                  if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false)) {
 156                      $conflicts[$modname] = $modfullname;
 157                  }
 158              }
 159          }
 160  
 161          foreach ($modnames as $modname => $modfullname) {
 162              // We do not display any custom completion rules, find modules that define them and add to conflicts list.
 163              if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_COMPLETION_HAS_RULES, false)) {
 164                  $conflicts[$modname] = $modfullname;
 165              }
 166          }
 167  
 168          return $conflicts;
 169      }
 170  
 171      /**
 172       * Form definition
 173       */
 174      public function definition() {
 175          $mform = $this->_form;
 176  
 177          // Course id.
 178          $mform->addElement('hidden', 'id', $this->course->id);
 179          $mform->setType('id', PARAM_INT);
 180  
 181          // Unlock completion automatically (this element can be used in validation).
 182          $mform->addElement('hidden', 'completionunlocked', 1);
 183          $mform->setType('completionunlocked', PARAM_INT);
 184  
 185          $mform->addElement('select', 'completion', get_string('completion', 'completion'),
 186              array(COMPLETION_TRACKING_NONE => get_string('completion_none', 'completion'),
 187                  COMPLETION_TRACKING_MANUAL => get_string('completion_manual', 'completion')));
 188          $mform->addHelpButton('completion', 'completion', 'completion');
 189          $mform->setDefault('completion', COMPLETION_TRACKING_NONE);
 190  
 191          // Automatic completion once you view it.
 192          $autocompletionpossible = false;
 193          if ($this->support_views()) {
 194              $mform->addElement('advcheckbox', 'completionview', get_string('completionview', 'completion'),
 195                  get_string('completionview_desc', 'completion'));
 196              $mform->disabledIf('completionview', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
 197              $autocompletionpossible = true;
 198          }
 199  
 200          // Automatic completion once it's graded.
 201          if ($this->support_grades()) {
 202              $group = [];
 203              $group[] = $mform->createElement('advcheckbox', 'completionusegrade', get_string('completionusegrade', 'completion'),
 204                  get_string('completionusegrade_desc', 'completion'));
 205              $mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion', '', true);
 206  
 207              $group[] = $mform->createElement('advcheckbox', 'completionpassgrade', get_string('completionpassgrade', 'completion'),
 208                  get_string('completionpassgrade_desc', 'completion'));
 209              $mform->addHelpButton('completionpassgrade', 'completionpassgrade', 'completion', '', true);
 210              $mform->disabledIf('completionpassgrade', 'completionusegrade', 'notchecked');
 211  
 212              $mform->addGroup($group, 'completionpassgroup', get_string('completionpassgrade', 'completion'), ' &nbsp; ', false);
 213              $mform->disabledIf('completionpassgroup', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
 214  
 215              $autocompletionpossible = true;
 216          }
 217  
 218          // Automatic completion according to module-specific rules.
 219          foreach ($this->add_custom_completion_rules() as $element) {
 220              $mform->disabledIf($element, 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
 221              $autocompletionpossible = true;
 222          }
 223  
 224          // Automatic option only appears if possible.
 225          if ($autocompletionpossible) {
 226              $mform->getElement('completion')->addOption(
 227                  get_string('completion_automatic', 'completion'),
 228                  COMPLETION_TRACKING_AUTOMATIC);
 229          }
 230  
 231          // Completion expected at particular date? (For progress tracking).
 232          $mform->addElement('date_time_selector', 'completionexpected',
 233              get_string('completionexpected', 'completion'), ['optional' => true]);
 234          $mform->addHelpButton('completionexpected', 'completionexpected', 'completion');
 235          $mform->disabledIf('completionexpected', 'completion', 'eq', COMPLETION_TRACKING_NONE);
 236  
 237          if ($conflicts = $this->get_modules_with_hidden_rules()) {
 238              $mform->addElement('static', 'qwerty', '', get_string('hiddenrules', 'completion', join(', ', $conflicts)));
 239          }
 240  
 241          $this->add_action_buttons();
 242      }
 243  
 244      /**
 245       * Form validation
 246       *
 247       * @param array $data array of ("fieldname"=>value) of submitted data
 248       * @param array $files array of uploaded files "element_name"=>tmp_file_path
 249       * @return array of "element_name"=>"error_description" if there are errors,
 250       *         or an empty array if everything is OK (true allowed for backwards compatibility too).
 251       */
 252      public function validation($data, $files) {
 253          $errors = parent::validation($data, $files);
 254  
 255          // Completion: Don't let them choose automatic completion without turning
 256          // on some conditions.
 257          if (array_key_exists('completion', $data) &&
 258              $data['completion'] == COMPLETION_TRACKING_AUTOMATIC) {
 259              if (empty($data['completionview']) && empty($data['completionusegrade']) && empty($data['completionpassgrade']) &&
 260                  !$this->completion_rule_enabled($data)) {
 261                  $errors['completion'] = get_string('badautocompletion', 'completion');
 262              }
 263          }
 264  
 265          return $errors;
 266      }
 267  
 268      /**
 269       * Returns if this form has custom completion rules. This is only possible if all selected modules have the same
 270       * module type and this module type supports custom completion rules
 271       *
 272       * @return bool
 273       */
 274      public function has_custom_completion_rules() {
 275          return $this->hascustomrules;
 276      }
 277  
 278      /**
 279       * Return submitted data if properly submitted or returns NULL if validation fails or
 280       * if there is no submitted data.
 281       *
 282       * @return object submitted data; NULL if not valid or not submitted or cancelled
 283       */
 284      public function get_data() {
 285          $data = parent::get_data();
 286          if ($data && $this->hascustomrules) {
 287              $this->get_module_form()->data_postprocessing($data);
 288          }
 289          return $data;
 290      }
 291  }