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->libdir.'/gradelib.php'); 21 require_once($CFG->libdir.'/plagiarismlib.php'); 22 23 use core_grades\component_gradeitems; 24 25 /** 26 * This class adds extra methods to form wrapper specific to be used for module add / update forms 27 * mod/{modname}/mod_form.php replaced deprecated mod/{modname}/mod.html Moodleform. 28 * 29 * @package core_course 30 * @copyright Andrew Nicols <andrew@nicols.co.uk> 31 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 32 */ 33 abstract class moodleform_mod extends moodleform { 34 35 use \core_completion\form\form_trait; 36 37 /** Current data */ 38 protected $current; 39 /** 40 * Instance of the module that is being updated. This is the id of the {prefix}{modulename} 41 * record. Can be used in form definition. Will be "" if this is an 'add' form and not an 42 * update one. 43 * 44 * @var mixed 45 */ 46 protected $_instance; 47 /** 48 * Section of course that module instance will be put in or is in. 49 * This is always the section number itself (column 'section' from 'course_sections' table). 50 * 51 * @var int 52 */ 53 protected $_section; 54 /** 55 * Course module record of the module that is being updated. Will be null if this is an 'add' form and not an 56 * update one. 57 * 58 * @var mixed 59 */ 60 protected $_cm; 61 62 /** 63 * Current course. 64 * 65 * @var mixed 66 */ 67 protected $_course; 68 69 /** 70 * List of modform features 71 */ 72 protected $_features; 73 /** 74 * @var array Custom completion-rule elements, if enabled 75 */ 76 protected $_customcompletionelements; 77 /** 78 * @var string name of module. 79 */ 80 protected $_modname; 81 /** current context, course or module depends if already exists*/ 82 protected $context; 83 84 /** a flag indicating whether outcomes are being used*/ 85 protected $_outcomesused; 86 87 /** 88 * @var bool A flag used to indicate that this module should lock settings 89 * based on admin settings flags in definition_after_data. 90 */ 91 protected $applyadminlockedflags = false; 92 93 /** @var object The course format of the current course. */ 94 protected $courseformat; 95 96 /** @var string Whether this is graded or rated. */ 97 private $gradedorrated = null; 98 99 public function __construct($current, $section, $cm, $course) { 100 global $CFG; 101 102 $this->current = $current; 103 $this->_instance = $current->instance; 104 $this->_section = $section; 105 $this->_cm = $cm; 106 $this->_course = $course; 107 if ($this->_cm) { 108 $this->context = context_module::instance($this->_cm->id); 109 } else { 110 $this->context = context_course::instance($course->id); 111 } 112 113 // Set the course format. 114 require_once($CFG->dirroot . '/course/format/lib.php'); 115 $this->courseformat = course_get_format($course); 116 117 // Guess module name if not set. 118 if (is_null($this->_modname)) { 119 $matches = array(); 120 if (!preg_match('/^mod_([^_]+)_mod_form$/', get_class($this), $matches)) { 121 debugging('Rename form to mod_xx_mod_form, where xx is name of your module'); 122 throw new \moodle_exception('unknownmodulename'); 123 } 124 $this->_modname = $matches[1]; 125 } 126 $this->init_features(); 127 parent::__construct('modedit.php'); 128 } 129 130 /** 131 * Old syntax of class constructor. Deprecated in PHP7. 132 * 133 * @deprecated since Moodle 3.1 134 */ 135 public function moodleform_mod($current, $section, $cm, $course) { 136 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 137 self::__construct($current, $section, $cm, $course); 138 } 139 140 /** 141 * Get the current data for the form. 142 * @return stdClass|null 143 */ 144 public function get_current() { 145 return $this->current; 146 } 147 148 /** 149 * Get the DB record for the current instance. 150 * @return stdClass|null 151 */ 152 public function get_instance() { 153 return $this->_instance; 154 } 155 156 /** 157 * Get the course section number (relative). 158 * @return int 159 */ 160 public function get_section() { 161 return $this->_section; 162 } 163 164 /** 165 * Get the course id. 166 * @return int 167 */ 168 public function get_course() { 169 return $this->_course; 170 } 171 172 /** 173 * Get the course module object. 174 * @return stdClass|null 175 */ 176 public function get_coursemodule() { 177 return $this->_cm; 178 } 179 180 /** 181 * Return the course context for new modules, or the module context for existing modules. 182 * @return context 183 */ 184 public function get_context() { 185 return $this->context; 186 } 187 188 /** 189 * Return the features this module supports. 190 * @return stdClass 191 */ 192 public function get_features() { 193 return $this->_features; 194 } 195 196 protected function init_features() { 197 global $CFG; 198 199 $this->_features = new stdClass(); 200 $this->_features->groups = plugin_supports('mod', $this->_modname, FEATURE_GROUPS, false); 201 $this->_features->groupings = plugin_supports('mod', $this->_modname, FEATURE_GROUPINGS, false); 202 $this->_features->outcomes = (!empty($CFG->enableoutcomes) and plugin_supports('mod', $this->_modname, FEATURE_GRADE_OUTCOMES, true)); 203 $this->_features->hasgrades = plugin_supports('mod', $this->_modname, FEATURE_GRADE_HAS_GRADE, false); 204 $this->_features->idnumber = plugin_supports('mod', $this->_modname, FEATURE_IDNUMBER, true); 205 $this->_features->introeditor = plugin_supports('mod', $this->_modname, FEATURE_MOD_INTRO, true); 206 $this->_features->defaultcompletion = plugin_supports('mod', $this->_modname, FEATURE_MODEDIT_DEFAULT_COMPLETION, true); 207 $this->_features->rating = plugin_supports('mod', $this->_modname, FEATURE_RATE, false); 208 $this->_features->showdescription = plugin_supports('mod', $this->_modname, FEATURE_SHOW_DESCRIPTION, false); 209 $this->_features->gradecat = ($this->_features->outcomes or $this->_features->hasgrades); 210 $this->_features->advancedgrading = plugin_supports('mod', $this->_modname, FEATURE_ADVANCED_GRADING, false); 211 $this->_features->hasnoview = plugin_supports('mod', $this->_modname, FEATURE_NO_VIEW_LINK, false); 212 $this->_features->canrescale = (component_callback_exists('mod_' . $this->_modname, 'rescale_activity_grades') !== false); 213 } 214 215 /** 216 * Allows module to modify data returned by get_moduleinfo_data() or prepare_new_moduleinfo_data() before calling set_data() 217 * This method is also called in the bulk activity completion form. 218 * 219 * Only available on moodleform_mod. 220 * 221 * @param array $default_values passed by reference 222 */ 223 function data_preprocessing(&$default_values){ 224 if (empty($default_values['scale'])) { 225 $default_values['assessed'] = 0; 226 } 227 228 if (empty($default_values['assessed'])){ 229 $default_values['ratingtime'] = 0; 230 } else { 231 $default_values['ratingtime']= 232 ($default_values['assesstimestart'] && $default_values['assesstimefinish']) ? 1 : 0; 233 } 234 235 // Amend completion settings. 236 if (isset($default_values['completiongradeitemnumber']) && 237 !is_null($default_values['completiongradeitemnumber'])) { 238 $default_values['receiveagrade'] = 1; 239 } 240 } 241 242 /** 243 * Each module which defines definition_after_data() must call this method using parent::definition_after_data(); 244 */ 245 function definition_after_data() { 246 global $CFG, $COURSE; 247 $mform =& $this->_form; 248 249 if ($id = $mform->getElementValue('update')) { 250 $modulename = $mform->getElementValue('modulename'); 251 $instance = $mform->getElementValue('instance'); 252 $component = "mod_{$modulename}"; 253 254 if ($this->_features->gradecat) { 255 $hasgradeitems = false; 256 $items = grade_item::fetch_all([ 257 'itemtype' => 'mod', 258 'itemmodule' => $modulename, 259 'iteminstance' => $instance, 260 'courseid' => $COURSE->id, 261 ]); 262 263 $gradecategories = []; 264 $removecategories = []; 265 //will be no items if, for example, this activity supports ratings but rating aggregate type == no ratings 266 if (!empty($items)) { 267 foreach ($items as $item) { 268 if (!empty($item->outcomeid)) { 269 $elname = 'outcome_'.$item->outcomeid; 270 if ($mform->elementExists($elname)) { 271 $mform->hardFreeze($elname); // prevent removing of existing outcomes 272 } 273 } else { 274 $hasgradeitems = true; 275 } 276 } 277 278 foreach ($items as $item) { 279 $gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber( 280 $component, 281 $item->itemnumber, 282 'gradecat' 283 ); 284 285 if (!isset($gradecategories[$gradecatfieldname])) { 286 $gradecategories[$gradecatfieldname] = $item->categoryid; 287 } else if ($gradecategories[$gradecatfieldname] != $item->categoryid) { 288 $removecategories[$gradecatfieldname] = true; 289 } 290 } 291 } 292 293 foreach ($removecategories as $toremove) { 294 if ($mform->elementExists($toremove)) { 295 $mform->removeElement($toremove); 296 } 297 } 298 } 299 } 300 301 if ($COURSE->groupmodeforce) { 302 if ($mform->elementExists('groupmode')) { 303 // The groupmode can not be changed if forced from course settings. 304 $mform->hardFreeze('groupmode'); 305 } 306 } 307 308 // Don't disable/remove groupingid if it is currently set to something, otherwise you cannot turn it off at same 309 // time as turning off other option (MDL-30764). 310 if (empty($this->_cm) || !$this->_cm->groupingid) { 311 if ($mform->elementExists('groupmode') && empty($COURSE->groupmodeforce)) { 312 $mform->hideIf('groupingid', 'groupmode', 'eq', NOGROUPS); 313 314 } else if (!$mform->elementExists('groupmode')) { 315 // Groupings have no use without groupmode. 316 if ($mform->elementExists('groupingid')) { 317 $mform->removeElement('groupingid'); 318 } 319 // Nor does the group restrictions button. 320 if ($mform->elementExists('restrictgroupbutton')) { 321 $mform->removeElement('restrictgroupbutton'); 322 } 323 } 324 } 325 326 // Completion: If necessary, freeze fields. 327 $cm = null; 328 if ($this->_cm) { 329 $cm = get_fast_modinfo($COURSE)->get_cm($this->_cm->id); 330 } 331 $this->definition_after_data_completion($cm); 332 333 // Freeze admin defaults if required (and not different from default) 334 $this->apply_admin_locked_flags(); 335 336 $this->plugin_extend_coursemodule_definition_after_data(); 337 } 338 339 // form verification 340 function validation($data, $files) { 341 global $COURSE, $DB, $CFG; 342 343 $mform =& $this->_form; 344 345 $errors = parent::validation($data, $files); 346 347 if ($mform->elementExists('name')) { 348 $name = trim($data['name']); 349 if ($name == '') { 350 $errors['name'] = get_string('required'); 351 } 352 } 353 354 $grade_item = grade_item::fetch(array('itemtype'=>'mod', 'itemmodule'=>$data['modulename'], 355 'iteminstance'=>$data['instance'], 'itemnumber'=>0, 'courseid'=>$COURSE->id)); 356 if ($data['coursemodule']) { 357 $cm = $DB->get_record('course_modules', array('id'=>$data['coursemodule'])); 358 } else { 359 $cm = null; 360 } 361 362 if ($mform->elementExists('cmidnumber')) { 363 // verify the idnumber 364 if (!grade_verify_idnumber($data['cmidnumber'], $COURSE->id, $grade_item, $cm)) { 365 $errors['cmidnumber'] = get_string('idnumbertaken'); 366 } 367 } 368 369 $component = "mod_{$this->_modname}"; 370 $itemnames = component_gradeitems::get_itemname_mapping_for_component($component); 371 foreach ($itemnames as $itemnumber => $itemname) { 372 $gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade'); 373 $gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradepass'); 374 $assessedfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'assessed'); 375 $scalefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'scale'); 376 377 // Ratings: Don't let them select an aggregate type without selecting a scale. 378 // If the user has selected to use ratings but has not chosen a scale or set max points then the form is 379 // invalid. If ratings have been selected then the user must select either a scale or max points. 380 // This matches (horrible) logic in data_preprocessing. 381 if (isset($data[$assessedfieldname]) && $data[$assessedfieldname] > 0 && empty($data[$scalefieldname])) { 382 $errors[$assessedfieldname] = get_string('scaleselectionrequired', 'rating'); 383 } 384 385 // Check that the grade pass is a valid number. 386 $gradepassvalid = false; 387 if (isset($data[$gradepassfieldname])) { 388 if (unformat_float($data[$gradepassfieldname], true) === false) { 389 $errors[$gradepassfieldname] = get_string('err_numeric', 'form'); 390 } else { 391 $gradepassvalid = true; 392 } 393 } 394 395 // Grade to pass: ensure that the grade to pass is valid for points and scales. 396 // If we are working with a scale, convert into a positive number for validation. 397 if ($gradepassvalid && isset($data[$gradepassfieldname]) && (!empty($data[$gradefieldname]) || !empty($data[$scalefieldname]))) { 398 $scale = !empty($data[$gradefieldname]) ? $data[$gradefieldname] : $data[$scalefieldname]; 399 if ($scale < 0) { 400 $scalevalues = $DB->get_record('scale', array('id' => -$scale)); 401 $grade = count(explode(',', $scalevalues->scale)); 402 } else { 403 $grade = $scale; 404 } 405 if (unformat_float($data[$gradepassfieldname]) > $grade) { 406 $errors[$gradepassfieldname] = get_string('gradepassgreaterthangrade', 'grades', $grade); 407 } 408 } 409 410 // We have a grade if there is a non-falsey value for: 411 // - the assessedfieldname for Ratings there; or 412 // - the gradefieldname for Ratings there. 413 if (empty($data[$assessedfieldname]) && empty($data[$gradefieldname])) { 414 // There are no grades set therefore completion is not allowed. 415 if (isset($data['completiongradeitemnumber']) && $data['completiongradeitemnumber'] == (string) $itemnumber) { 416 $errors['completiongradeitemnumber'] = get_string( 417 'badcompletiongradeitemnumber', 418 'completion', 419 get_string("grade_{$itemname}_name", $component) 420 ); 421 } 422 } 423 424 if (isset($data['completionpassgrade']) && $data['completionpassgrade']) { 425 // We need to check whether there's a valid gradepass value. 426 // This can either be in completiongradeitemnumber when there are multiple options OR, 427 // The first grade item if completionusegrade is specified. 428 $validategradepass = false; 429 if (isset($data['completiongradeitemnumber'])) { 430 if ($data['completiongradeitemnumber'] == (string)$itemnumber) { 431 $validategradepass = true; 432 } 433 } else if (isset($data['completionusegrade']) && $data['completionusegrade']) { 434 $validategradepass = true; 435 } 436 437 // We need to make all the validations related with $gradepassfieldname 438 // with them being correct floats, keeping the originals unmodified for 439 // later validations / showing the form back... 440 // TODO: Note that once MDL-73994 is fixed we'll have to re-visit this and 441 // adapt the code below to the new values arriving here, without forgetting 442 // the special case of empties and nulls. 443 $gradepass = isset($data[$gradepassfieldname]) ? unformat_float($data[$gradepassfieldname]) : null; 444 445 // Confirm gradepass is a valid non-empty (null or zero) value. 446 if ($validategradepass && (is_null($gradepass) || $gradepass == 0)) { 447 $errors['completionpassgrade'] = get_string( 448 'activitygradetopassnotset', 449 'completion' 450 ); 451 } 452 } 453 } 454 455 // Completion: Check completion fields don't have errors. 456 $errors = array_merge($errors, $this->validate_completion($data)); 457 458 // Availability: Check availability field does not have errors. 459 if (!empty($CFG->enableavailability)) { 460 \core_availability\frontend::report_validation_errors($data, $errors); 461 } 462 463 $pluginerrors = $this->plugin_extend_coursemodule_validation($data); 464 if (!empty($pluginerrors)) { 465 $errors = array_merge($errors, $pluginerrors); 466 } 467 468 return $errors; 469 } 470 471 /** 472 * Extend the validation function from any other plugin. 473 * 474 * @param stdClass $data The form data. 475 * @return array $errors The list of errors keyed by element name. 476 */ 477 protected function plugin_extend_coursemodule_validation($data) { 478 $errors = array(); 479 480 $callbacks = get_plugins_with_function('coursemodule_validation', 'lib.php'); 481 foreach ($callbacks as $type => $plugins) { 482 foreach ($plugins as $plugin => $pluginfunction) { 483 // We have exposed all the important properties with public getters - the errors array should be pass by reference. 484 $pluginerrors = $pluginfunction($this, $data); 485 if (!empty($pluginerrors)) { 486 $errors = array_merge($errors, $pluginerrors); 487 } 488 } 489 } 490 return $errors; 491 } 492 493 /** 494 * Load in existing data as form defaults. Usually new entry defaults are stored directly in 495 * form definition (new entry form); this function is used to load in data where values 496 * already exist and data is being edited (edit entry form). 497 * 498 * @param mixed $default_values object or array of default values 499 */ 500 function set_data($default_values) { 501 if (is_object($default_values)) { 502 $default_values = (array)$default_values; 503 } 504 505 $this->data_preprocessing($default_values); 506 parent::set_data($default_values); 507 } 508 509 /** 510 * Adds all the standard elements to a form to edit the settings for an activity module. 511 */ 512 protected function standard_coursemodule_elements() { 513 global $COURSE, $CFG, $DB, $OUTPUT; 514 $mform =& $this->_form; 515 516 $this->_outcomesused = false; 517 if ($this->_features->outcomes) { 518 if ($outcomes = grade_outcome::fetch_all_available($COURSE->id)) { 519 $this->_outcomesused = true; 520 $mform->addElement('header', 'modoutcomes', get_string('outcomes', 'grades')); 521 foreach($outcomes as $outcome) { 522 $mform->addElement('advcheckbox', 'outcome_'.$outcome->id, $outcome->get_name()); 523 } 524 } 525 } 526 527 if ($this->_features->rating) { 528 $this->add_rating_settings($mform, 0); 529 } 530 531 $mform->addElement('header', 'modstandardelshdr', get_string('modstandardels', 'form')); 532 533 $section = get_fast_modinfo($COURSE)->get_section_info($this->_section); 534 $allowstealth = 535 !empty($CFG->allowstealth) && 536 $this->courseformat->allow_stealth_module_visibility($this->_cm, $section) && 537 !$this->_features->hasnoview; 538 if ($allowstealth && $section->visible) { 539 $modvisiblelabel = 'modvisiblewithstealth'; 540 } else if ($section->visible) { 541 $modvisiblelabel = 'modvisible'; 542 } else { 543 $modvisiblelabel = 'modvisiblehiddensection'; 544 } 545 $mform->addElement('modvisible', 'visible', get_string($modvisiblelabel), null, 546 array('allowstealth' => $allowstealth, 'sectionvisible' => $section->visible, 'cm' => $this->_cm)); 547 $mform->addHelpButton('visible', $modvisiblelabel); 548 if (!empty($this->_cm) && !has_capability('moodle/course:activityvisibility', $this->get_context())) { 549 $mform->hardFreeze('visible'); 550 } 551 552 if ($this->_features->idnumber) { 553 $mform->addElement('text', 'cmidnumber', get_string('idnumbermod')); 554 $mform->setType('cmidnumber', PARAM_RAW); 555 $mform->addHelpButton('cmidnumber', 'idnumbermod'); 556 } 557 558 if (has_capability('moodle/course:setforcedlanguage', $this->get_context())) { 559 $languages = ['' => get_string('forceno')]; 560 $languages += get_string_manager()->get_list_of_translations(); 561 562 $mform->addElement('select', 'lang', get_string('forcelanguage'), $languages); 563 } 564 565 if ($CFG->downloadcoursecontentallowed) { 566 $choices = [ 567 DOWNLOAD_COURSE_CONTENT_DISABLED => get_string('no'), 568 DOWNLOAD_COURSE_CONTENT_ENABLED => get_string('yes'), 569 ]; 570 $mform->addElement('select', 'downloadcontent', get_string('downloadcontent', 'course'), $choices); 571 $downloadcontentdefault = $this->_cm->downloadcontent ?? DOWNLOAD_COURSE_CONTENT_ENABLED; 572 $mform->addHelpButton('downloadcontent', 'downloadcontent', 'course'); 573 if (has_capability('moodle/course:configuredownloadcontent', $this->get_context())) { 574 $mform->setDefault('downloadcontent', $downloadcontentdefault); 575 } else { 576 $mform->hardFreeze('downloadcontent'); 577 $mform->setConstant('downloadcontent', $downloadcontentdefault); 578 } 579 } 580 581 if ($this->_features->groups) { 582 $options = array(NOGROUPS => get_string('groupsnone'), 583 SEPARATEGROUPS => get_string('groupsseparate'), 584 VISIBLEGROUPS => get_string('groupsvisible')); 585 $mform->addElement('select', 'groupmode', get_string('groupmode', 'group'), $options, NOGROUPS); 586 $mform->addHelpButton('groupmode', 'groupmode', 'group'); 587 } 588 589 if ($this->_features->groupings) { 590 // Groupings selector - used to select grouping for groups in activity. 591 $options = array(); 592 if ($groupings = $DB->get_records('groupings', array('courseid'=>$COURSE->id))) { 593 foreach ($groupings as $grouping) { 594 $options[$grouping->id] = format_string($grouping->name); 595 } 596 } 597 core_collator::asort($options); 598 $options = array(0 => get_string('none')) + $options; 599 $mform->addElement('select', 'groupingid', get_string('grouping', 'group'), $options); 600 $mform->addHelpButton('groupingid', 'grouping', 'group'); 601 } 602 603 if (!empty($CFG->enableavailability)) { 604 // Add special button to end of previous section if groups/groupings 605 // are enabled. 606 607 $availabilityplugins = \core\plugininfo\availability::get_enabled_plugins(); 608 $groupavailability = $this->_features->groups && array_key_exists('group', $availabilityplugins); 609 $groupingavailability = $this->_features->groupings && array_key_exists('grouping', $availabilityplugins); 610 611 if ($groupavailability || $groupingavailability) { 612 // When creating the button, we need to set type=button to prevent it behaving as a submit. 613 $mform->addElement('static', 'restrictgroupbutton', '', 614 html_writer::tag('button', get_string('restrictbygroup', 'availability'), [ 615 'id' => 'restrictbygroup', 616 'type' => 'button', 617 'disabled' => 'disabled', 618 'class' => 'btn btn-secondary', 619 'data-groupavailability' => $groupavailability, 620 'data-groupingavailability' => $groupingavailability 621 ]) 622 ); 623 } 624 625 // Availability field. This is just a textarea; the user interface 626 // interaction is all implemented in JavaScript. 627 $mform->addElement('header', 'availabilityconditionsheader', 628 get_string('restrictaccess', 'availability')); 629 // Note: This field cannot be named 'availability' because that 630 // conflicts with fields in existing modules (such as assign). 631 // So it uses a long name that will not conflict. 632 $mform->addElement('textarea', 'availabilityconditionsjson', 633 get_string('accessrestrictions', 'availability'), 634 ['class' => 'd-none'] 635 ); 636 // Availability loading indicator. 637 $loadingcontainer = $OUTPUT->container( 638 $OUTPUT->render_from_template('core/loading', []), 639 'd-flex justify-content-center py-5 icon-size-5', 640 'availabilityconditions-loading' 641 ); 642 $mform->addElement('html', $loadingcontainer); 643 644 // The _cm variable may not be a proper cm_info, so get one from modinfo. 645 if ($this->_cm) { 646 $modinfo = get_fast_modinfo($COURSE); 647 $cm = $modinfo->get_cm($this->_cm->id); 648 } else { 649 $cm = null; 650 } 651 \core_availability\frontend::include_all_javascript($COURSE, $cm); 652 } 653 654 // Conditional activities: completion tracking section 655 if(!isset($completion)) { 656 $completion = new completion_info($COURSE); 657 } 658 659 // Add the completion tracking elements to the form. 660 if ($completion->is_enabled()) { 661 $mform->addElement('header', 'activitycompletionheader', get_string('activitycompletion', 'completion')); 662 $this->add_completion_elements(null, false, false, false, $this->_course->id); 663 } 664 665 // Populate module tags. 666 if (core_tag_tag::is_enabled('core', 'course_modules')) { 667 $mform->addElement('header', 'tagshdr', get_string('tags', 'tag')); 668 $mform->addElement('tags', 'tags', get_string('tags'), array('itemtype' => 'course_modules', 'component' => 'core')); 669 if ($this->_cm) { 670 $tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $this->_cm->id); 671 $mform->setDefault('tags', $tags); 672 } 673 } 674 675 $this->standard_hidden_coursemodule_elements(); 676 677 $this->plugin_extend_coursemodule_standard_elements(); 678 } 679 680 /** 681 * Add rating settings. 682 * 683 * @param moodleform_mod $mform 684 * @param int $itemnumber 685 */ 686 protected function add_rating_settings($mform, int $itemnumber) { 687 global $CFG, $COURSE; 688 689 if ($this->gradedorrated && $this->gradedorrated !== 'rated') { 690 return; 691 } 692 $this->gradedorrated = 'rated'; 693 694 require_once("{$CFG->dirroot}/rating/lib.php"); 695 $rm = new rating_manager(); 696 697 $component = "mod_{$this->_modname}"; 698 $gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradecat'); 699 $gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradepass'); 700 $assessedfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'assessed'); 701 $scalefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'scale'); 702 703 $mform->addElement('header', 'modstandardratings', get_string('ratings', 'rating')); 704 705 $isupdate = !empty($this->_cm); 706 707 $rolenamestring = null; 708 if ($isupdate) { 709 $capabilities = ['moodle/rating:rate', "mod/{$this->_cm->modname}:rate"]; 710 $rolenames = get_role_names_with_caps_in_context($this->get_context(), $capabilities); 711 $rolenamestring = implode(', ', $rolenames); 712 } else { 713 $rolenamestring = get_string('capabilitychecknotavailable', 'rating'); 714 } 715 716 $mform->addElement('static', 'rolewarning', get_string('rolewarning', 'rating'), $rolenamestring); 717 $mform->addHelpButton('rolewarning', 'rolewarning', 'rating'); 718 719 $mform->addElement('select', $assessedfieldname, get_string('aggregatetype', 'rating') , $rm->get_aggregate_types()); 720 $mform->setDefault($assessedfieldname, 0); 721 $mform->addHelpButton($assessedfieldname, 'aggregatetype', 'rating'); 722 723 $gradeoptions = [ 724 'isupdate' => $isupdate, 725 'currentgrade' => false, 726 'hasgrades' => false, 727 'canrescale' => false, 728 'useratings' => true, 729 ]; 730 if ($isupdate) { 731 $gradeitem = grade_item::fetch([ 732 'itemtype' => 'mod', 733 'itemmodule' => $this->_cm->modname, 734 'iteminstance' => $this->_cm->instance, 735 'itemnumber' => $itemnumber, 736 'courseid' => $COURSE->id, 737 ]); 738 if ($gradeitem) { 739 $gradeoptions['currentgrade'] = $gradeitem->grademax; 740 $gradeoptions['currentgradetype'] = $gradeitem->gradetype; 741 $gradeoptions['currentscaleid'] = $gradeitem->scaleid; 742 $gradeoptions['hasgrades'] = $gradeitem->has_grades(); 743 } 744 } 745 746 $mform->addElement('modgrade', $scalefieldname, get_string('scale'), $gradeoptions); 747 $mform->hideIf($scalefieldname, $assessedfieldname, 'eq', 0); 748 $mform->addHelpButton($scalefieldname, 'modgrade', 'grades'); 749 $mform->setDefault($scalefieldname, $CFG->gradepointdefault); 750 751 $mform->addElement('checkbox', 'ratingtime', get_string('ratingtime', 'rating')); 752 $mform->hideIf('ratingtime', $assessedfieldname, 'eq', 0); 753 754 $mform->addElement('date_time_selector', 'assesstimestart', get_string('from')); 755 $mform->hideIf('assesstimestart', $assessedfieldname, 'eq', 0); 756 $mform->hideIf('assesstimestart', 'ratingtime'); 757 758 $mform->addElement('date_time_selector', 'assesstimefinish', get_string('to')); 759 $mform->hideIf('assesstimefinish', $assessedfieldname, 'eq', 0); 760 $mform->hideIf('assesstimefinish', 'ratingtime'); 761 762 if ($this->_features->gradecat) { 763 $mform->addElement( 764 'select', 765 $gradecatfieldname, 766 get_string('gradecategoryonmodform', 'grades'), 767 grade_get_categories_menu($COURSE->id, $this->_outcomesused) 768 ); 769 $mform->addHelpButton($gradecatfieldname, 'gradecategoryonmodform', 'grades'); 770 $mform->hideIf($gradecatfieldname, $assessedfieldname, 'eq', 0); 771 $mform->hideIf($gradecatfieldname, "{$scalefieldname}[modgrade_type]", 'eq', 'none'); 772 } 773 774 // Grade to pass. 775 $mform->addElement('float', $gradepassfieldname, get_string('gradepass', 'grades')); 776 $mform->addHelpButton($gradepassfieldname, 'gradepass', 'grades'); 777 $mform->setDefault($gradepassfieldname, ''); 778 $mform->hideIf($gradepassfieldname, $assessedfieldname, 'eq', '0'); 779 $mform->hideIf($gradepassfieldname, "{$scalefieldname}[modgrade_type]", 'eq', 'none'); 780 } 781 782 /** 783 * Plugins can extend the coursemodule settings form. 784 */ 785 protected function plugin_extend_coursemodule_standard_elements() { 786 $callbacks = get_plugins_with_function('coursemodule_standard_elements', 'lib.php'); 787 foreach ($callbacks as $type => $plugins) { 788 foreach ($plugins as $plugin => $pluginfunction) { 789 // We have exposed all the important properties with public getters - and the callback can manipulate the mform 790 // directly. 791 $pluginfunction($this, $this->_form); 792 } 793 } 794 } 795 796 /** 797 * Plugins can extend the coursemodule settings form after the data is set. 798 */ 799 protected function plugin_extend_coursemodule_definition_after_data() { 800 $callbacks = get_plugins_with_function('coursemodule_definition_after_data', 'lib.php'); 801 foreach ($callbacks as $type => $plugins) { 802 foreach ($plugins as $plugin => $pluginfunction) { 803 $pluginfunction($this, $this->_form); 804 } 805 } 806 } 807 808 /** 809 * Can be overridden to add custom completion rules if the module wishes 810 * them. If overriding this, you should also override completion_rule_enabled. 811 * <p> 812 * Just add elements to the form as needed and return the list of IDs. The 813 * system will call disabledIf and handle other behaviour for each returned 814 * ID. 815 * @return array Array of string IDs of added items, empty array if none 816 */ 817 function add_completion_rules() { 818 return array(); 819 } 820 821 /** 822 * Called during validation. Override to indicate, based on the data, whether 823 * a custom completion rule is enabled (selected). 824 * 825 * @param array $data Input data (not yet validated) 826 * @return bool True if one or more rules is enabled, false if none are; 827 * default returns false 828 */ 829 function completion_rule_enabled($data) { 830 return false; 831 } 832 833 /** 834 * Add completion grading elements to the form and return the list of element ids. 835 * 836 * @return array Array of string IDs of added items, empty array if none 837 */ 838 public function add_completiongrade_rules(): array { 839 return []; 840 } 841 842 function standard_hidden_coursemodule_elements(){ 843 $mform =& $this->_form; 844 $mform->addElement('hidden', 'course', 0); 845 $mform->setType('course', PARAM_INT); 846 847 $mform->addElement('hidden', 'coursemodule', 0); 848 $mform->setType('coursemodule', PARAM_INT); 849 850 $mform->addElement('hidden', 'section', 0); 851 $mform->setType('section', PARAM_INT); 852 853 $mform->addElement('hidden', 'module', 0); 854 $mform->setType('module', PARAM_INT); 855 856 $mform->addElement('hidden', 'modulename', ''); 857 $mform->setType('modulename', PARAM_PLUGIN); 858 859 $mform->addElement('hidden', 'instance', 0); 860 $mform->setType('instance', PARAM_INT); 861 862 $mform->addElement('hidden', 'add', 0); 863 $mform->setType('add', PARAM_ALPHANUM); 864 865 $mform->addElement('hidden', 'update', 0); 866 $mform->setType('update', PARAM_INT); 867 868 $mform->addElement('hidden', 'return', 0); 869 $mform->setType('return', PARAM_BOOL); 870 871 $mform->addElement('hidden', 'sr', 0); 872 $mform->setType('sr', PARAM_INT); 873 874 $mform->addElement('hidden', 'beforemod', 0); 875 $mform->setType('beforemod', PARAM_INT); 876 877 $mform->addElement('hidden', 'showonly', ''); 878 $mform->setType('showonly', PARAM_ALPHANUMEXT); 879 } 880 881 public function standard_grading_coursemodule_elements() { 882 global $COURSE, $CFG; 883 884 if ($this->gradedorrated && $this->gradedorrated !== 'graded') { 885 return; 886 } 887 if ($this->_features->rating) { 888 return; 889 } 890 $this->gradedorrated = 'graded'; 891 892 $itemnumber = 0; 893 $component = "mod_{$this->_modname}"; 894 $gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade'); 895 $gradecatfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradecat'); 896 $gradepassfieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'gradepass'); 897 898 $mform =& $this->_form; 899 $isupdate = !empty($this->_cm); 900 $gradeoptions = array('isupdate' => $isupdate, 901 'currentgrade' => false, 902 'hasgrades' => false, 903 'canrescale' => $this->_features->canrescale, 904 'useratings' => $this->_features->rating); 905 906 if ($this->_features->hasgrades) { 907 if ($this->_features->gradecat) { 908 $mform->addElement('header', 'modstandardgrade', get_string('gradenoun')); 909 } 910 911 //if supports grades and grades arent being handled via ratings 912 if ($isupdate) { 913 $gradeitem = grade_item::fetch(array('itemtype' => 'mod', 914 'itemmodule' => $this->_cm->modname, 915 'iteminstance' => $this->_cm->instance, 916 'itemnumber' => 0, 917 'courseid' => $COURSE->id)); 918 if ($gradeitem) { 919 $gradeoptions['currentgrade'] = $gradeitem->grademax; 920 $gradeoptions['currentgradetype'] = $gradeitem->gradetype; 921 $gradeoptions['currentscaleid'] = $gradeitem->scaleid; 922 $gradeoptions['hasgrades'] = $gradeitem->has_grades(); 923 } 924 } 925 $mform->addElement('modgrade', $gradefieldname, get_string('gradenoun'), $gradeoptions); 926 $mform->addHelpButton($gradefieldname, 'modgrade', 'grades'); 927 $mform->setDefault($gradefieldname, $CFG->gradepointdefault); 928 929 if ($this->_features->advancedgrading 930 and !empty($this->current->_advancedgradingdata['methods']) 931 and !empty($this->current->_advancedgradingdata['areas'])) { 932 933 if (count($this->current->_advancedgradingdata['areas']) == 1) { 934 // if there is just one gradable area (most cases), display just the selector 935 // without its name to make UI simplier 936 $areadata = reset($this->current->_advancedgradingdata['areas']); 937 $areaname = key($this->current->_advancedgradingdata['areas']); 938 $mform->addElement('select', 'advancedgradingmethod_'.$areaname, 939 get_string('gradingmethod', 'core_grading'), $this->current->_advancedgradingdata['methods']); 940 $mform->addHelpButton('advancedgradingmethod_'.$areaname, 'gradingmethod', 'core_grading'); 941 $mform->hideIf('advancedgradingmethod_'.$areaname, "{$gradefieldname}[modgrade_type]", 'eq', 'none'); 942 943 } else { 944 // the module defines multiple gradable areas, display a selector 945 // for each of them together with a name of the area 946 $areasgroup = array(); 947 foreach ($this->current->_advancedgradingdata['areas'] as $areaname => $areadata) { 948 $areasgroup[] = $mform->createElement('select', 'advancedgradingmethod_'.$areaname, 949 $areadata['title'], $this->current->_advancedgradingdata['methods']); 950 $areasgroup[] = $mform->createElement('static', 'advancedgradingareaname_'.$areaname, '', $areadata['title']); 951 } 952 $mform->addGroup($areasgroup, 'advancedgradingmethodsgroup', get_string('gradingmethods', 'core_grading'), 953 array(' ', '<br />'), false); 954 } 955 } 956 957 if ($this->_features->gradecat) { 958 $mform->addElement('select', $gradecatfieldname, 959 get_string('gradecategoryonmodform', 'grades'), 960 grade_get_categories_menu($COURSE->id, $this->_outcomesused)); 961 $mform->addHelpButton($gradecatfieldname, 'gradecategoryonmodform', 'grades'); 962 $mform->hideIf($gradecatfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none'); 963 } 964 965 // Grade to pass. 966 $mform->addElement('float', $gradepassfieldname, get_string($gradepassfieldname, 'grades')); 967 $mform->addHelpButton($gradepassfieldname, $gradepassfieldname, 'grades'); 968 $mform->setDefault($gradepassfieldname, ''); 969 $mform->hideIf($gradepassfieldname, "{$gradefieldname}[modgrade_type]", 'eq', 'none'); 970 } 971 } 972 973 /** 974 * Add an editor for an activity's introduction field. 975 * @deprecated since MDL-49101 - use moodleform_mod::standard_intro_elements() instead. 976 * @param null $required Override system default for requiremodintro 977 * @param null $customlabel Override default label for editor 978 * @throws coding_exception 979 */ 980 protected function add_intro_editor($required=null, $customlabel=null) { 981 $str = "Function moodleform_mod::add_intro_editor() is deprecated, use moodleform_mod::standard_intro_elements() instead."; 982 debugging($str, DEBUG_DEVELOPER); 983 984 $this->standard_intro_elements($customlabel); 985 } 986 987 988 /** 989 * Add an editor for an activity's introduction field. 990 * 991 * @param null $customlabel Override default label for editor 992 * @throws coding_exception 993 */ 994 protected function standard_intro_elements($customlabel=null) { 995 global $CFG; 996 997 $required = $CFG->requiremodintro; 998 999 $mform = $this->_form; 1000 $label = is_null($customlabel) ? get_string('moduleintro') : $customlabel; 1001 1002 $mform->addElement('editor', 'introeditor', $label, array('rows' => 10), array('maxfiles' => EDITOR_UNLIMITED_FILES, 1003 'noclean' => true, 'context' => $this->context, 'subdirs' => true)); 1004 $mform->setType('introeditor', PARAM_RAW); // no XSS prevention here, users must be trusted 1005 if ($required) { 1006 $mform->addRule('introeditor', get_string('required'), 'required', null, 'client'); 1007 } 1008 1009 // If the 'show description' feature is enabled, this checkbox appears below the intro. 1010 // We want to hide that when using the singleactivity course format because it is confusing. 1011 if ($this->_features->showdescription && $this->courseformat->has_view_page()) { 1012 $mform->addElement('advcheckbox', 'showdescription', get_string('showdescription')); 1013 $mform->addHelpButton('showdescription', 'showdescription'); 1014 } 1015 } 1016 1017 /** 1018 * Overriding formslib's add_action_buttons() method, to add an extra submit "save changes and return" button. 1019 * 1020 * @param bool $cancel show cancel button 1021 * @param string $submitlabel null means default, false means none, string is label text 1022 * @param string $submit2label null means default, false means none, string is label text 1023 * @return void 1024 */ 1025 function add_action_buttons($cancel=true, $submitlabel=null, $submit2label=null) { 1026 if (is_null($submitlabel)) { 1027 $submitlabel = get_string('savechangesanddisplay'); 1028 } 1029 1030 if (is_null($submit2label)) { 1031 $submit2label = get_string('savechangesandreturntocourse'); 1032 } 1033 1034 $mform = $this->_form; 1035 1036 $mform->addElement('checkbox', 'coursecontentnotification', get_string('coursecontentnotification', 'course')); 1037 $mform->addHelpButton('coursecontentnotification', 'coursecontentnotification', 'course'); 1038 $mform->closeHeaderBefore('coursecontentnotification'); 1039 1040 // elements in a row need a group 1041 $buttonarray = array(); 1042 1043 // Label for the submit button to return to the course. 1044 // Ignore this button in single activity format because it is confusing. 1045 if ($submit2label !== false && $this->courseformat->has_view_page()) { 1046 $buttonarray[] = &$mform->createElement('submit', 'submitbutton2', $submit2label); 1047 } 1048 1049 if ($submitlabel !== false) { 1050 $buttonarray[] = &$mform->createElement('submit', 'submitbutton', $submitlabel); 1051 } 1052 1053 if ($cancel) { 1054 $buttonarray[] = &$mform->createElement('cancel'); 1055 } 1056 1057 $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); 1058 $mform->setType('buttonar', PARAM_RAW); 1059 } 1060 1061 /** 1062 * Get the list of admin settings for this module and apply any locked settings. 1063 * This cannot happen in apply_admin_defaults because we do not the current values of the settings 1064 * in that function because set_data has not been called yet. 1065 * 1066 * @return void 1067 */ 1068 protected function apply_admin_locked_flags() { 1069 global $OUTPUT; 1070 1071 if (!$this->applyadminlockedflags) { 1072 return; 1073 } 1074 1075 $settings = get_config($this->_modname); 1076 $mform = $this->_form; 1077 $lockedicon = html_writer::tag('span', 1078 $OUTPUT->pix_icon('t/locked', get_string('locked', 'admin')), 1079 array('class' => 'action-icon')); 1080 $isupdate = !empty($this->_cm); 1081 1082 foreach ($settings as $name => $value) { 1083 if (strpos('_', $name) !== false) { 1084 continue; 1085 } 1086 if ($mform->elementExists($name)) { 1087 $element = $mform->getElement($name); 1088 $lockedsetting = $name . '_locked'; 1089 if (!empty($settings->$lockedsetting)) { 1090 // Always lock locked settings for new modules, 1091 // for updates, only lock them if the current value is the same as the default (or there is no current value). 1092 $value = $settings->$name; 1093 if ($isupdate && isset($this->current->$name)) { 1094 $value = $this->current->$name; 1095 } 1096 if ($value == $settings->$name) { 1097 $mform->setConstant($name, $settings->$name); 1098 $element->setLabel($element->getLabel() . $lockedicon); 1099 // Do not use hardfreeze because we need the hidden input to check dependencies. 1100 $element->freeze(); 1101 } 1102 } 1103 } 1104 } 1105 } 1106 1107 /** 1108 * Get the list of admin settings for this module and apply any defaults/advanced/locked/required settings. 1109 * 1110 * @param $datetimeoffsets array - If passed, this is an array of fieldnames => times that the 1111 * default date/time value should be relative to. If not passed, all 1112 * date/time fields are set relative to the users current midnight. 1113 * @return void 1114 */ 1115 public function apply_admin_defaults($datetimeoffsets = array()) { 1116 // This flag triggers the settings to be locked in apply_admin_locked_flags(). 1117 $this->applyadminlockedflags = true; 1118 1119 $settings = get_config($this->_modname); 1120 $mform = $this->_form; 1121 $usermidnight = usergetmidnight(time()); 1122 $isupdate = !empty($this->_cm); 1123 1124 foreach ($settings as $name => $value) { 1125 if (strpos('_', $name) !== false) { 1126 continue; 1127 } 1128 if ($mform->elementExists($name)) { 1129 $element = $mform->getElement($name); 1130 if (!$isupdate) { 1131 if ($element->getType() == 'date_time_selector') { 1132 $enabledsetting = $name . '_enabled'; 1133 if (empty($settings->$enabledsetting)) { 1134 $mform->setDefault($name, 0); 1135 } else { 1136 $relativetime = $usermidnight; 1137 if (isset($datetimeoffsets[$name])) { 1138 $relativetime = $datetimeoffsets[$name]; 1139 } 1140 $mform->setDefault($name, $relativetime + $settings->$name); 1141 } 1142 } else { 1143 $mform->setDefault($name, $settings->$name); 1144 } 1145 } 1146 $advancedsetting = $name . '_adv'; 1147 if (!empty($settings->$advancedsetting)) { 1148 $mform->setAdvanced($name); 1149 } 1150 $requiredsetting = $name . '_required'; 1151 if (!empty($settings->$requiredsetting)) { 1152 $mform->addRule($name, null, 'required', null, 'client'); 1153 } 1154 } 1155 } 1156 } 1157 1158 /** 1159 * Allows modules to modify the data returned by form get_data(). 1160 * This method is also called in the bulk activity completion form. 1161 * 1162 * Only available on moodleform_mod. 1163 * 1164 * @param stdClass $data passed by reference 1165 */ 1166 public function data_postprocessing($data) { 1167 } 1168 1169 /** 1170 * Return submitted data if properly submitted or returns NULL if validation fails or 1171 * if there is no submitted data. 1172 * 1173 * Do not override this method, override data_postprocessing() instead. 1174 * 1175 * @return object submitted data; NULL if not valid or not submitted or cancelled 1176 */ 1177 public function get_data() { 1178 $data = parent::get_data(); 1179 if ($data) { 1180 // Convert the grade pass value - we may be using a language which uses commas, 1181 // rather than decimal points, in numbers. These need to be converted so that 1182 // they can be added to the DB. 1183 if (isset($data->gradepass)) { 1184 $data->gradepass = unformat_float($data->gradepass); 1185 } 1186 1187 // Trim name for all activity name. 1188 if (isset($data->name)) { 1189 $data->name = trim($data->name); 1190 } 1191 1192 $this->data_postprocessing($data); 1193 } 1194 return $data; 1195 } 1196 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body