PageRenderTime 53ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/mod/quiz/mod_form.php

https://bitbucket.org/moodle/moodle
PHP | 663 lines | 452 code | 95 blank | 116 comment | 80 complexity | 4eaf9aad5f5b8647ef2b74576eeb2620 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
  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. * Defines the quiz module ettings form.
  18. *
  19. * @package mod_quiz
  20. * @copyright 2006 Jamie Pratt
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. require_once($CFG->dirroot . '/course/moodleform_mod.php');
  25. require_once($CFG->dirroot . '/mod/quiz/locallib.php');
  26. /**
  27. * Settings form for the quiz module.
  28. *
  29. * @copyright 2006 Jamie Pratt
  30. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31. */
  32. class mod_quiz_mod_form extends moodleform_mod {
  33. /** @var array options to be used with date_time_selector fields in the quiz. */
  34. public static $datefieldoptions = array('optional' => true);
  35. protected $_feedbacks;
  36. protected static $reviewfields = array(); // Initialised in the constructor.
  37. /** @var int the max number of attempts allowed in any user or group override on this quiz. */
  38. protected $maxattemptsanyoverride = null;
  39. public function __construct($current, $section, $cm, $course) {
  40. self::$reviewfields = array(
  41. 'attempt' => array('theattempt', 'quiz'),
  42. 'correctness' => array('whethercorrect', 'question'),
  43. 'marks' => array('marks', 'quiz'),
  44. 'specificfeedback' => array('specificfeedback', 'question'),
  45. 'generalfeedback' => array('generalfeedback', 'question'),
  46. 'rightanswer' => array('rightanswer', 'question'),
  47. 'overallfeedback' => array('reviewoverallfeedback', 'quiz'),
  48. );
  49. parent::__construct($current, $section, $cm, $course);
  50. }
  51. protected function definition() {
  52. global $COURSE, $CFG, $DB, $PAGE;
  53. $quizconfig = get_config('quiz');
  54. $mform = $this->_form;
  55. // -------------------------------------------------------------------------------
  56. $mform->addElement('header', 'general', get_string('general', 'form'));
  57. // Name.
  58. $mform->addElement('text', 'name', get_string('name'), array('size'=>'64'));
  59. if (!empty($CFG->formatstringstriptags)) {
  60. $mform->setType('name', PARAM_TEXT);
  61. } else {
  62. $mform->setType('name', PARAM_CLEANHTML);
  63. }
  64. $mform->addRule('name', null, 'required', null, 'client');
  65. $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
  66. // Introduction.
  67. $this->standard_intro_elements(get_string('introduction', 'quiz'));
  68. // -------------------------------------------------------------------------------
  69. $mform->addElement('header', 'timing', get_string('timing', 'quiz'));
  70. // Open and close dates.
  71. $mform->addElement('date_time_selector', 'timeopen', get_string('quizopen', 'quiz'),
  72. self::$datefieldoptions);
  73. $mform->addHelpButton('timeopen', 'quizopenclose', 'quiz');
  74. $mform->addElement('date_time_selector', 'timeclose', get_string('quizclose', 'quiz'),
  75. self::$datefieldoptions);
  76. // Time limit.
  77. $mform->addElement('duration', 'timelimit', get_string('timelimit', 'quiz'),
  78. array('optional' => true));
  79. $mform->addHelpButton('timelimit', 'timelimit', 'quiz');
  80. // What to do with overdue attempts.
  81. $mform->addElement('select', 'overduehandling', get_string('overduehandling', 'quiz'),
  82. quiz_get_overdue_handling_options());
  83. $mform->addHelpButton('overduehandling', 'overduehandling', 'quiz');
  84. // TODO Formslib does OR logic on disableif, and we need AND logic here.
  85. // $mform->disabledIf('overduehandling', 'timelimit', 'eq', 0);
  86. // $mform->disabledIf('overduehandling', 'timeclose', 'eq', 0);
  87. // Grace period time.
  88. $mform->addElement('duration', 'graceperiod', get_string('graceperiod', 'quiz'),
  89. array('optional' => true));
  90. $mform->addHelpButton('graceperiod', 'graceperiod', 'quiz');
  91. $mform->hideIf('graceperiod', 'overduehandling', 'neq', 'graceperiod');
  92. // -------------------------------------------------------------------------------
  93. // Grade settings.
  94. $this->standard_grading_coursemodule_elements();
  95. $mform->removeElement('grade');
  96. if (property_exists($this->current, 'grade')) {
  97. $currentgrade = $this->current->grade;
  98. } else {
  99. $currentgrade = $quizconfig->maximumgrade;
  100. }
  101. $mform->addElement('hidden', 'grade', $currentgrade);
  102. $mform->setType('grade', PARAM_FLOAT);
  103. // Number of attempts.
  104. $attemptoptions = array('0' => get_string('unlimited'));
  105. for ($i = 1; $i <= QUIZ_MAX_ATTEMPT_OPTION; $i++) {
  106. $attemptoptions[$i] = $i;
  107. }
  108. $mform->addElement('select', 'attempts', get_string('attemptsallowed', 'quiz'),
  109. $attemptoptions);
  110. // Grading method.
  111. $mform->addElement('select', 'grademethod', get_string('grademethod', 'quiz'),
  112. quiz_get_grading_options());
  113. $mform->addHelpButton('grademethod', 'grademethod', 'quiz');
  114. if ($this->get_max_attempts_for_any_override() < 2) {
  115. $mform->hideIf('grademethod', 'attempts', 'eq', 1);
  116. }
  117. // -------------------------------------------------------------------------------
  118. $mform->addElement('header', 'layouthdr', get_string('layout', 'quiz'));
  119. $pagegroup = array();
  120. $pagegroup[] = $mform->createElement('select', 'questionsperpage',
  121. get_string('newpage', 'quiz'), quiz_questions_per_page_options(), array('id' => 'id_questionsperpage'));
  122. $mform->setDefault('questionsperpage', $quizconfig->questionsperpage);
  123. if (!empty($this->_cm)) {
  124. $pagegroup[] = $mform->createElement('checkbox', 'repaginatenow', '',
  125. get_string('repaginatenow', 'quiz'), array('id' => 'id_repaginatenow'));
  126. }
  127. $mform->addGroup($pagegroup, 'questionsperpagegrp',
  128. get_string('newpage', 'quiz'), null, false);
  129. $mform->addHelpButton('questionsperpagegrp', 'newpage', 'quiz');
  130. $mform->setAdvanced('questionsperpagegrp', $quizconfig->questionsperpage_adv);
  131. // Navigation method.
  132. $mform->addElement('select', 'navmethod', get_string('navmethod', 'quiz'),
  133. quiz_get_navigation_options());
  134. $mform->addHelpButton('navmethod', 'navmethod', 'quiz');
  135. // -------------------------------------------------------------------------------
  136. $mform->addElement('header', 'interactionhdr', get_string('questionbehaviour', 'quiz'));
  137. // Shuffle within questions.
  138. $mform->addElement('selectyesno', 'shuffleanswers', get_string('shufflewithin', 'quiz'));
  139. $mform->addHelpButton('shuffleanswers', 'shufflewithin', 'quiz');
  140. // How questions behave (question behaviour).
  141. if (!empty($this->current->preferredbehaviour)) {
  142. $currentbehaviour = $this->current->preferredbehaviour;
  143. } else {
  144. $currentbehaviour = '';
  145. }
  146. $behaviours = question_engine::get_behaviour_options($currentbehaviour);
  147. $mform->addElement('select', 'preferredbehaviour',
  148. get_string('howquestionsbehave', 'question'), $behaviours);
  149. $mform->addHelpButton('preferredbehaviour', 'howquestionsbehave', 'question');
  150. // Can redo completed questions.
  151. $redochoices = array(0 => get_string('no'), 1 => get_string('canredoquestionsyes', 'quiz'));
  152. $mform->addElement('select', 'canredoquestions', get_string('canredoquestions', 'quiz'), $redochoices);
  153. $mform->addHelpButton('canredoquestions', 'canredoquestions', 'quiz');
  154. foreach ($behaviours as $behaviour => $notused) {
  155. if (!question_engine::can_questions_finish_during_the_attempt($behaviour)) {
  156. $mform->hideIf('canredoquestions', 'preferredbehaviour', 'eq', $behaviour);
  157. }
  158. }
  159. // Each attempt builds on last.
  160. $mform->addElement('selectyesno', 'attemptonlast',
  161. get_string('eachattemptbuildsonthelast', 'quiz'));
  162. $mform->addHelpButton('attemptonlast', 'eachattemptbuildsonthelast', 'quiz');
  163. if ($this->get_max_attempts_for_any_override() < 2) {
  164. $mform->hideIf('attemptonlast', 'attempts', 'eq', 1);
  165. }
  166. // -------------------------------------------------------------------------------
  167. $mform->addElement('header', 'reviewoptionshdr',
  168. get_string('reviewoptionsheading', 'quiz'));
  169. $mform->addHelpButton('reviewoptionshdr', 'reviewoptionsheading', 'quiz');
  170. // Review options.
  171. $this->add_review_options_group($mform, $quizconfig, 'during',
  172. mod_quiz_display_options::DURING, true);
  173. $this->add_review_options_group($mform, $quizconfig, 'immediately',
  174. mod_quiz_display_options::IMMEDIATELY_AFTER);
  175. $this->add_review_options_group($mform, $quizconfig, 'open',
  176. mod_quiz_display_options::LATER_WHILE_OPEN);
  177. $this->add_review_options_group($mform, $quizconfig, 'closed',
  178. mod_quiz_display_options::AFTER_CLOSE);
  179. foreach ($behaviours as $behaviour => $notused) {
  180. $unusedoptions = question_engine::get_behaviour_unused_display_options($behaviour);
  181. foreach ($unusedoptions as $unusedoption) {
  182. $mform->disabledIf($unusedoption . 'during', 'preferredbehaviour',
  183. 'eq', $behaviour);
  184. }
  185. }
  186. $mform->disabledIf('attemptduring', 'preferredbehaviour',
  187. 'neq', 'wontmatch');
  188. $mform->disabledIf('overallfeedbackduring', 'preferredbehaviour',
  189. 'neq', 'wontmatch');
  190. foreach (self::$reviewfields as $field => $notused) {
  191. $mform->disabledIf($field . 'closed', 'timeclose[enabled]');
  192. }
  193. // -------------------------------------------------------------------------------
  194. $mform->addElement('header', 'display', get_string('appearance'));
  195. // Show user picture.
  196. $mform->addElement('select', 'showuserpicture', get_string('showuserpicture', 'quiz'),
  197. quiz_get_user_image_options());
  198. $mform->addHelpButton('showuserpicture', 'showuserpicture', 'quiz');
  199. // Overall decimal points.
  200. $options = array();
  201. for ($i = 0; $i <= QUIZ_MAX_DECIMAL_OPTION; $i++) {
  202. $options[$i] = $i;
  203. }
  204. $mform->addElement('select', 'decimalpoints', get_string('decimalplaces', 'quiz'),
  205. $options);
  206. $mform->addHelpButton('decimalpoints', 'decimalplaces', 'quiz');
  207. // Question decimal points.
  208. $options = array(-1 => get_string('sameasoverall', 'quiz'));
  209. for ($i = 0; $i <= QUIZ_MAX_Q_DECIMAL_OPTION; $i++) {
  210. $options[$i] = $i;
  211. }
  212. $mform->addElement('select', 'questiondecimalpoints',
  213. get_string('decimalplacesquestion', 'quiz'), $options);
  214. $mform->addHelpButton('questiondecimalpoints', 'decimalplacesquestion', 'quiz');
  215. // Show blocks during quiz attempt.
  216. $mform->addElement('selectyesno', 'showblocks', get_string('showblocks', 'quiz'));
  217. $mform->addHelpButton('showblocks', 'showblocks', 'quiz');
  218. // -------------------------------------------------------------------------------
  219. $mform->addElement('header', 'security', get_string('extraattemptrestrictions', 'quiz'));
  220. // Require password to begin quiz attempt.
  221. $mform->addElement('passwordunmask', 'quizpassword', get_string('requirepassword', 'quiz'));
  222. $mform->setType('quizpassword', PARAM_TEXT);
  223. $mform->addHelpButton('quizpassword', 'requirepassword', 'quiz');
  224. // IP address.
  225. $mform->addElement('text', 'subnet', get_string('requiresubnet', 'quiz'));
  226. $mform->setType('subnet', PARAM_TEXT);
  227. $mform->addHelpButton('subnet', 'requiresubnet', 'quiz');
  228. // Enforced time delay between quiz attempts.
  229. $mform->addElement('duration', 'delay1', get_string('delay1st2nd', 'quiz'),
  230. array('optional' => true));
  231. $mform->addHelpButton('delay1', 'delay1st2nd', 'quiz');
  232. if ($this->get_max_attempts_for_any_override() < 2) {
  233. $mform->hideIf('delay1', 'attempts', 'eq', 1);
  234. }
  235. $mform->addElement('duration', 'delay2', get_string('delaylater', 'quiz'),
  236. array('optional' => true));
  237. $mform->addHelpButton('delay2', 'delaylater', 'quiz');
  238. if ($this->get_max_attempts_for_any_override() < 3) {
  239. $mform->hideIf('delay2', 'attempts', 'eq', 1);
  240. $mform->hideIf('delay2', 'attempts', 'eq', 2);
  241. }
  242. // Browser security choices.
  243. $mform->addElement('select', 'browsersecurity', get_string('browsersecurity', 'quiz'),
  244. quiz_access_manager::get_browser_security_choices());
  245. $mform->addHelpButton('browsersecurity', 'browsersecurity', 'quiz');
  246. // Any other rule plugins.
  247. quiz_access_manager::add_settings_form_fields($this, $mform);
  248. // -------------------------------------------------------------------------------
  249. $mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'quiz'));
  250. $mform->addHelpButton('overallfeedbackhdr', 'overallfeedback', 'quiz');
  251. if (isset($this->current->grade)) {
  252. $needwarning = $this->current->grade === 0;
  253. } else {
  254. $needwarning = $quizconfig->maximumgrade == 0;
  255. }
  256. if ($needwarning) {
  257. $mform->addElement('static', 'nogradewarning', '',
  258. get_string('nogradewarning', 'quiz'));
  259. }
  260. $mform->addElement('static', 'gradeboundarystatic1',
  261. get_string('gradeboundary', 'quiz'), '100%');
  262. $repeatarray = array();
  263. $repeatedoptions = array();
  264. $repeatarray[] = $mform->createElement('editor', 'feedbacktext',
  265. get_string('feedback', 'quiz'), array('rows' => 3), array('maxfiles' => EDITOR_UNLIMITED_FILES,
  266. 'noclean' => true, 'context' => $this->context));
  267. $repeatarray[] = $mform->createElement('text', 'feedbackboundaries',
  268. get_string('gradeboundary', 'quiz'), array('size' => 10));
  269. $repeatedoptions['feedbacktext']['type'] = PARAM_RAW;
  270. $repeatedoptions['feedbackboundaries']['type'] = PARAM_RAW;
  271. if (!empty($this->_instance)) {
  272. $this->_feedbacks = $DB->get_records('quiz_feedback',
  273. array('quizid' => $this->_instance), 'mingrade DESC');
  274. $numfeedbacks = count($this->_feedbacks);
  275. } else {
  276. $this->_feedbacks = array();
  277. $numfeedbacks = $quizconfig->initialnumfeedbacks;
  278. }
  279. $numfeedbacks = max($numfeedbacks, 1);
  280. $nextel = $this->repeat_elements($repeatarray, $numfeedbacks - 1,
  281. $repeatedoptions, 'boundary_repeats', 'boundary_add_fields', 3,
  282. get_string('addmoreoverallfeedbacks', 'quiz'), true);
  283. // Put some extra elements in before the button.
  284. $mform->insertElementBefore($mform->createElement('editor',
  285. "feedbacktext[$nextel]", get_string('feedback', 'quiz'), array('rows' => 3),
  286. array('maxfiles' => EDITOR_UNLIMITED_FILES, 'noclean' => true,
  287. 'context' => $this->context)),
  288. 'boundary_add_fields');
  289. $mform->insertElementBefore($mform->createElement('static',
  290. 'gradeboundarystatic2', get_string('gradeboundary', 'quiz'), '0%'),
  291. 'boundary_add_fields');
  292. // Add the disabledif rules. We cannot do this using the $repeatoptions parameter to
  293. // repeat_elements because we don't want to dissable the first feedbacktext.
  294. for ($i = 0; $i < $nextel; $i++) {
  295. $mform->disabledIf('feedbackboundaries[' . $i . ']', 'grade', 'eq', 0);
  296. $mform->disabledIf('feedbacktext[' . ($i + 1) . ']', 'grade', 'eq', 0);
  297. }
  298. // -------------------------------------------------------------------------------
  299. $this->standard_coursemodule_elements();
  300. // Check and act on whether setting outcomes is considered an advanced setting.
  301. $mform->setAdvanced('modoutcomes', !empty($quizconfig->outcomes_adv));
  302. // The standard_coursemodule_elements method sets this to 100, but the
  303. // quiz has its own setting, so use that.
  304. $mform->setDefault('grade', $quizconfig->maximumgrade);
  305. // -------------------------------------------------------------------------------
  306. $this->apply_admin_defaults();
  307. $this->add_action_buttons();
  308. $PAGE->requires->yui_module('moodle-mod_quiz-modform', 'M.mod_quiz.modform.init');
  309. }
  310. protected function add_review_options_group($mform, $quizconfig, $whenname,
  311. $when, $withhelp = false) {
  312. global $OUTPUT;
  313. $group = array();
  314. foreach (self::$reviewfields as $field => $string) {
  315. list($identifier, $component) = $string;
  316. $label = get_string($identifier, $component);
  317. $group[] = $mform->createElement('html', html_writer::start_div('review_option_item'));
  318. $el = $mform->createElement('checkbox', $field . $whenname, '', $label);
  319. if ($withhelp) {
  320. $el->_helpbutton = $OUTPUT->render(new help_icon($identifier, $component));
  321. }
  322. $group[] = $el;
  323. $group[] = $mform->createElement('html', html_writer::end_div());
  324. }
  325. $mform->addGroup($group, $whenname . 'optionsgrp',
  326. get_string('review' . $whenname, 'quiz'), null, false);
  327. foreach (self::$reviewfields as $field => $notused) {
  328. $cfgfield = 'review' . $field;
  329. if ($quizconfig->$cfgfield & $when) {
  330. $mform->setDefault($field . $whenname, 1);
  331. } else {
  332. $mform->setDefault($field . $whenname, 0);
  333. }
  334. }
  335. if ($whenname != 'during') {
  336. $mform->disabledIf('correctness' . $whenname, 'attempt' . $whenname);
  337. $mform->disabledIf('specificfeedback' . $whenname, 'attempt' . $whenname);
  338. $mform->disabledIf('generalfeedback' . $whenname, 'attempt' . $whenname);
  339. $mform->disabledIf('rightanswer' . $whenname, 'attempt' . $whenname);
  340. }
  341. }
  342. protected function preprocessing_review_settings(&$toform, $whenname, $when) {
  343. foreach (self::$reviewfields as $field => $notused) {
  344. $fieldname = 'review' . $field;
  345. if (array_key_exists($fieldname, $toform)) {
  346. $toform[$field . $whenname] = $toform[$fieldname] & $when;
  347. }
  348. }
  349. }
  350. public function data_preprocessing(&$toform) {
  351. if (isset($toform['grade'])) {
  352. // Convert to a real number, so we don't get 0.0000.
  353. $toform['grade'] = $toform['grade'] + 0;
  354. }
  355. if (count($this->_feedbacks)) {
  356. $key = 0;
  357. foreach ($this->_feedbacks as $feedback) {
  358. $draftid = file_get_submitted_draft_itemid('feedbacktext['.$key.']');
  359. $toform['feedbacktext['.$key.']']['text'] = file_prepare_draft_area(
  360. $draftid, // Draftid.
  361. $this->context->id, // Context.
  362. 'mod_quiz', // Component.
  363. 'feedback', // Filarea.
  364. !empty($feedback->id) ? (int) $feedback->id : null, // Itemid.
  365. null,
  366. $feedback->feedbacktext // Text.
  367. );
  368. $toform['feedbacktext['.$key.']']['format'] = $feedback->feedbacktextformat;
  369. $toform['feedbacktext['.$key.']']['itemid'] = $draftid;
  370. if ($toform['grade'] == 0) {
  371. // When a quiz is un-graded, there can only be one lot of
  372. // feedback. If the quiz previously had a maximum grade and
  373. // several lots of feedback, we must now avoid putting text
  374. // into input boxes that are disabled, but which the
  375. // validation will insist are blank.
  376. break;
  377. }
  378. if ($feedback->mingrade > 0) {
  379. $toform['feedbackboundaries['.$key.']'] =
  380. round(100.0 * $feedback->mingrade / $toform['grade'], 6) . '%';
  381. }
  382. $key++;
  383. }
  384. }
  385. if (isset($toform['timelimit'])) {
  386. $toform['timelimitenable'] = $toform['timelimit'] > 0;
  387. }
  388. $this->preprocessing_review_settings($toform, 'during',
  389. mod_quiz_display_options::DURING);
  390. $this->preprocessing_review_settings($toform, 'immediately',
  391. mod_quiz_display_options::IMMEDIATELY_AFTER);
  392. $this->preprocessing_review_settings($toform, 'open',
  393. mod_quiz_display_options::LATER_WHILE_OPEN);
  394. $this->preprocessing_review_settings($toform, 'closed',
  395. mod_quiz_display_options::AFTER_CLOSE);
  396. $toform['attemptduring'] = true;
  397. $toform['overallfeedbackduring'] = false;
  398. // Password field - different in form to stop browsers that remember
  399. // passwords from getting confused.
  400. if (isset($toform['password'])) {
  401. $toform['quizpassword'] = $toform['password'];
  402. unset($toform['password']);
  403. }
  404. // Load any settings belonging to the access rules.
  405. if (!empty($toform['instance'])) {
  406. $accesssettings = quiz_access_manager::load_settings($toform['instance']);
  407. foreach ($accesssettings as $name => $value) {
  408. $toform[$name] = $value;
  409. }
  410. }
  411. if (empty($toform['completionminattempts'])) {
  412. $toform['completionminattempts'] = 1;
  413. } else {
  414. $toform['completionminattemptsenabled'] = $toform['completionminattempts'] > 0;
  415. }
  416. }
  417. /**
  418. * Allows module to modify the data returned by form get_data().
  419. * This method is also called in the bulk activity completion form.
  420. *
  421. * Only available on moodleform_mod.
  422. *
  423. * @param stdClass $data the form data to be modified.
  424. */
  425. public function data_postprocessing($data) {
  426. parent::data_postprocessing($data);
  427. if (!empty($data->completionunlocked)) {
  428. // Turn off completion settings if the checkboxes aren't ticked.
  429. $autocompletion = !empty($data->completion) && $data->completion == COMPLETION_TRACKING_AUTOMATIC;
  430. if (empty($data->completionminattemptsenabled) || !$autocompletion) {
  431. $data->completionminattempts = 0;
  432. }
  433. }
  434. }
  435. public function validation($data, $files) {
  436. $errors = parent::validation($data, $files);
  437. // Check open and close times are consistent.
  438. if ($data['timeopen'] != 0 && $data['timeclose'] != 0 &&
  439. $data['timeclose'] < $data['timeopen']) {
  440. $errors['timeclose'] = get_string('closebeforeopen', 'quiz');
  441. }
  442. // Check that the grace period is not too short.
  443. if ($data['overduehandling'] == 'graceperiod') {
  444. $graceperiodmin = get_config('quiz', 'graceperiodmin');
  445. if ($data['graceperiod'] <= $graceperiodmin) {
  446. $errors['graceperiod'] = get_string('graceperiodtoosmall', 'quiz', format_time($graceperiodmin));
  447. }
  448. }
  449. if (!empty($data['completionminattempts'])) {
  450. if ($data['attempts'] > 0 && $data['completionminattempts'] > $data['attempts']) {
  451. $errors['completionminattemptsgroup'] = get_string('completionminattemptserror', 'quiz');
  452. }
  453. }
  454. // Check the boundary value is a number or a percentage, and in range.
  455. $i = 0;
  456. while (!empty($data['feedbackboundaries'][$i] )) {
  457. $boundary = trim($data['feedbackboundaries'][$i]);
  458. if (strlen($boundary) > 0) {
  459. if ($boundary[strlen($boundary) - 1] == '%') {
  460. $boundary = trim(substr($boundary, 0, -1));
  461. if (is_numeric($boundary)) {
  462. $boundary = $boundary * $data['grade'] / 100.0;
  463. } else {
  464. $errors["feedbackboundaries[$i]"] =
  465. get_string('feedbackerrorboundaryformat', 'quiz', $i + 1);
  466. }
  467. } else if (!is_numeric($boundary)) {
  468. $errors["feedbackboundaries[$i]"] =
  469. get_string('feedbackerrorboundaryformat', 'quiz', $i + 1);
  470. }
  471. }
  472. if (is_numeric($boundary) && $boundary <= 0 || $boundary >= $data['grade'] ) {
  473. $errors["feedbackboundaries[$i]"] =
  474. get_string('feedbackerrorboundaryoutofrange', 'quiz', $i + 1);
  475. }
  476. if (is_numeric($boundary) && $i > 0 &&
  477. $boundary >= $data['feedbackboundaries'][$i - 1]) {
  478. $errors["feedbackboundaries[$i]"] =
  479. get_string('feedbackerrororder', 'quiz', $i + 1);
  480. }
  481. $data['feedbackboundaries'][$i] = $boundary;
  482. $i += 1;
  483. }
  484. $numboundaries = $i;
  485. // Check there is nothing in the remaining unused fields.
  486. if (!empty($data['feedbackboundaries'])) {
  487. for ($i = $numboundaries; $i < count($data['feedbackboundaries']); $i += 1) {
  488. if (!empty($data['feedbackboundaries'][$i] ) &&
  489. trim($data['feedbackboundaries'][$i] ) != '') {
  490. $errors["feedbackboundaries[$i]"] =
  491. get_string('feedbackerrorjunkinboundary', 'quiz', $i + 1);
  492. }
  493. }
  494. }
  495. for ($i = $numboundaries + 1; $i < count($data['feedbacktext']); $i += 1) {
  496. if (!empty($data['feedbacktext'][$i]['text']) &&
  497. trim($data['feedbacktext'][$i]['text'] ) != '') {
  498. $errors["feedbacktext[$i]"] =
  499. get_string('feedbackerrorjunkinfeedback', 'quiz', $i + 1);
  500. }
  501. }
  502. // If CBM is involved, don't show the warning for grade to pass being larger than the maximum grade.
  503. if (($data['preferredbehaviour'] == 'deferredcbm') OR ($data['preferredbehaviour'] == 'immediatecbm')) {
  504. unset($errors['gradepass']);
  505. }
  506. // Any other rule plugins.
  507. $errors = quiz_access_manager::validate_settings_form_fields($errors, $data, $files, $this);
  508. return $errors;
  509. }
  510. /**
  511. * Display module-specific activity completion rules.
  512. * Part of the API defined by moodleform_mod
  513. * @return array Array of string IDs of added items, empty array if none
  514. */
  515. public function add_completion_rules() {
  516. $mform = $this->_form;
  517. $items = array();
  518. $mform->addElement('advcheckbox', 'completionattemptsexhausted', null,
  519. get_string('completionattemptsexhausted', 'quiz'),
  520. array('group' => 'cattempts'));
  521. $mform->disabledIf('completionattemptsexhausted', 'completionpassgrade', 'notchecked');
  522. $items[] = 'completionattemptsexhausted';
  523. $group = array();
  524. $group[] = $mform->createElement('checkbox', 'completionminattemptsenabled', '',
  525. get_string('completionminattempts', 'quiz'));
  526. $group[] = $mform->createElement('text', 'completionminattempts', '', array('size' => 3));
  527. $mform->setType('completionminattempts', PARAM_INT);
  528. $mform->addGroup($group, 'completionminattemptsgroup', get_string('completionminattemptsgroup', 'quiz'), array(' '), false);
  529. $mform->disabledIf('completionminattempts', 'completionminattemptsenabled', 'notchecked');
  530. $items[] = 'completionminattemptsgroup';
  531. return $items;
  532. }
  533. /**
  534. * Called during validation. Indicates whether a module-specific completion rule is selected.
  535. *
  536. * @param array $data Input data (not yet validated)
  537. * @return bool True if one or more rules is enabled, false if none are.
  538. */
  539. public function completion_rule_enabled($data) {
  540. return !empty($data['completionattemptsexhausted']) ||
  541. !empty($data['completionminattemptsenabled']);
  542. }
  543. /**
  544. * Get the maximum number of attempts that anyone might have due to a user
  545. * or group override. Used to decide whether disabledIf rules should be applied.
  546. * @return int the number of attempts allowed. For the purpose of this method,
  547. * unlimited is returned as 1000, not 0.
  548. */
  549. public function get_max_attempts_for_any_override() {
  550. global $DB;
  551. if (empty($this->_instance)) {
  552. // Quiz not created yet, so no overrides.
  553. return 1;
  554. }
  555. if ($this->maxattemptsanyoverride === null) {
  556. $this->maxattemptsanyoverride = $DB->get_field_sql("
  557. SELECT MAX(CASE WHEN attempts = 0 THEN 1000 ELSE attempts END)
  558. FROM {quiz_overrides}
  559. WHERE quiz = ?",
  560. array($this->_instance));
  561. if ($this->maxattemptsanyoverride < 1) {
  562. // This happens when no override alters the number of attempts.
  563. $this->maxattemptsanyoverride = 1;
  564. }
  565. }
  566. return $this->maxattemptsanyoverride;
  567. }
  568. }