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 310 and 403] [Versions 311 and 403] [Versions 39 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  defined('MOODLE_INTERNAL') || die;
  18  
  19  require_once($CFG->libdir.'/formslib.php');
  20  require_once($CFG->dirroot.'/course/modlib.php');
  21  
  22  /**
  23   * Base form for changing completion rules. Used in bulk editing activity completion and editing default activity completion
  24   *
  25   * @package     core_completion
  26   * @copyright   2017 Marina Glancy
  27   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   */
  29  abstract class core_completion_edit_base_form extends moodleform {
  30  
  31      use \core_completion\form\form_trait;
  32  
  33      /** @var moodleform_mod Do not use directly, call $this->get_module_form() */
  34      protected $_moduleform = null;
  35      /** @var bool */
  36      protected $hascustomrules = false;
  37      /** @var stdClass */
  38      protected $course;
  39  
  40      /**
  41       * Returns list of types of selected module types
  42       *
  43       * @return array modname=>modfullname
  44       */
  45      abstract protected function get_module_names();
  46  
  47      /**
  48       * Get the module name. If the form have more than one modules, it will return the first one.
  49       *
  50       * @return string|null The module name or null if there is no modules associated to this form.
  51       */
  52      protected function get_module_name(): ?string {
  53          $modnames = $this->get_module_names();
  54          if (empty($modnames)) {
  55              return null;
  56          }
  57  
  58          $modnamekeys = array_keys($modnames);
  59          return reset($modnamekeys);
  60      }
  61  
  62      /**
  63       * Returns true if all selected modules support tracking view.
  64       *
  65       * @return bool
  66       */
  67      protected function support_views() {
  68          foreach ($this->get_module_names() as $modname => $modfullname) {
  69              if (!plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false)) {
  70                  return false;
  71              }
  72          }
  73          return true;
  74      }
  75  
  76      /**
  77       * Returns true if all selected modules support grading.
  78       *
  79       * @return bool
  80       */
  81      protected function support_grades() {
  82          foreach ($this->get_module_names() as $modname => $modfullname) {
  83              if (!plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false)) {
  84                  return false;
  85              }
  86          }
  87          return true;
  88      }
  89  
  90      /**
  91       * Returns an instance of component-specific module form for the first selected module
  92       *
  93       * @return moodleform_mod|null
  94       */
  95      abstract protected function get_module_form();
  96  
  97      /**
  98       * If all selected modules are of the same module type, adds custom completion rules from this module type
  99       *
 100       * @return array
 101       */
 102      protected function add_custom_completion(string $function): array {
 103          $modnames = array_keys($this->get_module_names());
 104  
 105          if (count($modnames) != 1 || !plugin_supports('mod', $modnames[0], FEATURE_COMPLETION_HAS_RULES, false)) {
 106              return [false, []];
 107          }
 108  
 109          $component = "mod_{$modnames[0]}";
 110          $itemnames = \core_grades\component_gradeitems::get_itemname_mapping_for_component($component);
 111          $hascustomrules = count($itemnames) > 1;
 112  
 113          try {
 114              // Add completion rules from the module form to this form.
 115              $moduleform = $this->get_module_form();
 116              $moduleform->_form = $this->_form;
 117              if ($customcompletionelements = $moduleform->{$function}()) {
 118                  $hascustomrules = true;
 119                  foreach ($customcompletionelements as $customcompletionelement) {
 120                      // Instead of checking for the suffix at the end of the element name, we need to check for its presence
 121                      // because some modules, like SCORM, are adding things at the end.
 122                      if (!str_contains($customcompletionelement, $this->get_suffix())) {
 123                          debugging(
 124                              'Custom completion rule '  . $customcompletionelement . ' of module ' . $modnames[0] .
 125                              ' has wrong suffix and has been removed from the form. This has to be fixed by the developer',
 126                              DEBUG_DEVELOPER
 127                          );
 128                          if ($moduleform->_form->elementExists($customcompletionelement)) {
 129                              $moduleform->_form->removeElement($customcompletionelement);
 130                          }
 131                      }
 132                  }
 133              }
 134              return [$hascustomrules, $customcompletionelements];
 135          } catch (Exception $e) {
 136              debugging('Could not add custom completion rule of module ' . $modnames[0] .
 137                  ' to this form, this has to be fixed by the developer', DEBUG_DEVELOPER);
 138              return [$hascustomrules, $customcompletionelements];
 139          }
 140      }
 141  
 142      /**
 143       * If all selected modules are of the same module type, adds custom completion rules from this module type
 144       *
 145       * @return array
 146       */
 147      protected function add_completion_rules() {
 148          list($hascustomrules, $customcompletionelements) = $this->add_custom_completion('add_completion_rules');
 149          if (!$this->hascustomrules && $hascustomrules) {
 150              $this->hascustomrules = true;
 151          }
 152  
 153          $component = "mod_{$this->get_module_name()}";
 154          $itemnames = \core_grades\component_gradeitems::get_itemname_mapping_for_component($component);
 155          if (count($itemnames) > 1) {
 156              $customcompletionelements[] = 'completiongradeitemnumber';
 157          }
 158  
 159          return $customcompletionelements;
 160      }
 161  
 162      /**
 163       * Checks if at least one of the custom completion rules is enabled
 164       *
 165       * @param array $data Input data (not yet validated)
 166       * @return bool True if one or more rules is enabled, false if none are;
 167       *   default returns false
 168       */
 169      protected function completion_rule_enabled($data) {
 170          if ($this->hascustomrules) {
 171              return $this->get_module_form()->completion_rule_enabled($data);
 172          }
 173          return false;
 174      }
 175  
 176      /**
 177       * If all selected modules are of the same module type, adds custom completion rules from this module type
 178       *
 179       * @return array
 180       */
 181      public function add_completiongrade_rules(): array {
 182          list($hascustomrules, $customcompletionelements) = $this->add_custom_completion('add_completiongrade_rules');
 183          if (!$this->hascustomrules && $hascustomrules) {
 184              $this->hascustomrules = true;
 185          }
 186  
 187          return $customcompletionelements;
 188      }
 189  
 190      /**
 191       * Returns list of modules that have automatic completion rules that are not shown on this form
 192       * (because they are not present in at least one other selected module).
 193       *
 194       * @return array
 195       */
 196      protected function get_modules_with_hidden_rules() {
 197          $modnames = $this->get_module_names();
 198          if (count($modnames) <= 1) {
 199              // No rules definitions conflicts if there is only one module type.
 200              return [];
 201          }
 202  
 203          $conflicts = [];
 204  
 205          if (!$this->support_views()) {
 206              // If we don't display views rule but at least one module supports it - we have conflicts.
 207              foreach ($modnames as $modname => $modfullname) {
 208                  if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false)) {
 209                      $conflicts[$modname] = $modfullname;
 210                  }
 211              }
 212          }
 213  
 214          if (!$this->support_grades()) {
 215              // If we don't display grade rule but at least one module supports it - we have conflicts.
 216              foreach ($modnames as $modname => $modfullname) {
 217                  if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false)) {
 218                      $conflicts[$modname] = $modfullname;
 219                  }
 220              }
 221          }
 222  
 223          foreach ($modnames as $modname => $modfullname) {
 224              // We do not display any custom completion rules, find modules that define them and add to conflicts list.
 225              if (empty($conflicts[$modname]) && plugin_supports('mod', $modname, FEATURE_COMPLETION_HAS_RULES, false)) {
 226                  $conflicts[$modname] = $modfullname;
 227              }
 228          }
 229  
 230          return $conflicts;
 231      }
 232  
 233      /**
 234       * Form definition
 235       */
 236      public function definition() {
 237          $mform = $this->_form;
 238  
 239          // Course id.
 240          $mform->addElement('hidden', 'id', $this->course->id);
 241          $mform->setType('id', PARAM_INT);
 242  
 243          // Add the completion elements to the form.
 244          $this->add_completion_elements(
 245              $this->get_module_name(),
 246              $this->support_views(),
 247              $this->support_grades(),
 248              false,
 249              $this->course->id
 250          );
 251  
 252          if ($conflicts = $this->get_modules_with_hidden_rules()) {
 253              $mform->addElement('static', 'qwerty', '', get_string('hiddenrules', 'completion', join(', ', $conflicts)));
 254          }
 255  
 256          // Whether to show the cancel button or not in the form.
 257          $displaycancel = $this->_customdata['displaycancel'] ?? true;
 258          $this->add_action_buttons($displaycancel);
 259      }
 260  
 261      /**
 262       * Return the course module of the form, if any.
 263       *
 264       * @return cm_info|null
 265       */
 266      protected function get_cm(): ?cm_info {
 267          return null;
 268      }
 269  
 270      /**
 271       * Each module which defines definition_after_data() must call this method.
 272       */
 273      public function definition_after_data() {
 274          $this->definition_after_data_completion($this->get_cm());
 275      }
 276  
 277      /**
 278       * Form validation
 279       *
 280       * @param array $data array of ("fieldname"=>value) of submitted data
 281       * @param array $files array of uploaded files "element_name"=>tmp_file_path
 282       * @return array of "element_name"=>"error_description" if there are errors,
 283       *         or an empty array if everything is OK (true allowed for backwards compatibility too).
 284       */
 285      public function validation($data, $files) {
 286          $errors = parent::validation($data, $files);
 287  
 288          // Completion: Check completion fields don't have errors.
 289          $errors = array_merge($errors, $this->validate_completion($data));
 290  
 291          return $errors;
 292      }
 293  
 294      /**
 295       * Returns if this form has custom completion rules. This is only possible if all selected modules have the same
 296       * module type and this module type supports custom completion rules
 297       *
 298       * @return bool
 299       */
 300      public function has_custom_completion_rules() {
 301          return $this->hascustomrules;
 302      }
 303  
 304      /**
 305       * Return submitted data if properly submitted or returns NULL if validation fails or
 306       * if there is no submitted data.
 307       *
 308       * @return object submitted data; NULL if not valid or not submitted or cancelled
 309       */
 310      public function get_data() {
 311          $data = parent::get_data();
 312          if ($data && $this->hascustomrules) {
 313              $this->get_module_form()->data_postprocessing($data);
 314          }
 315          return $data;
 316      }
 317  }