Merge branch 'MDL-64286' of https://github.com/timhunt/moodle
[moodle.git] / question / type / gapselect / edit_form_base.php
CommitLineData
0d24b17a 1<?php
f7dd2d44
TH
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 class for editing question types like this one.
19 *
647d9373 20 * @package qtype_gapselect
9df0480d
TH
21 * @copyright 2011 The Open University
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
f7dd2d44
TH
23 */
24
b28ad86a
TH
25defined('MOODLE_INTERNAL') || die();
26
27
0d24b17a
TH
28/**
29 * Elements embedded in question text editing form definition.
30 *
9df0480d
TH
31 * @copyright 2011 The Open University
32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0d24b17a
TH
33 */
34class qtype_gapselect_edit_form_base extends question_edit_form {
35
36 /** @var array of HTML tags allowed in choices / drag boxes. */
37 protected $allowedhtmltags = array(
38 'sub',
39 'sup',
40 'b',
41 'i',
42 'em',
43 'strong'
44 );
45
46 /** @var string regex to match HTML open tags. */
8d6fb0c6 47 private $htmltstarttagsandattributes = '~<\s*\w+\b[^>]*>~';
0d24b17a
TH
48
49 /** @var string regex to match HTML close tags or br. */
8d6fb0c6 50 private $htmltclosetags = '~<\s*/\s*\w+\b[^>]*>~';
0d24b17a
TH
51
52 /** @var string regex to select text like [[cat]] (including the square brackets). */
060e0294 53 private $squarebracketsregex = '/\[\[[^]]*?\]\]/';
0d24b17a 54
8d6fb0c6
TH
55 /**
56 * Vaidate some input to make sure it does not contain any tags other than
57 * $this->allowedhtmltags.
58 * @param unknown_type $text the input to validate.
59 * @return string any validation errors.
60 */
61 protected function get_illegal_tag_error($text) {
62 // Remove legal tags.
63 $strippedtext = $text;
0d24b17a 64 foreach ($this->allowedhtmltags as $htmltag) {
8d6fb0c6
TH
65 $tagpair = "~<\s*/?\s*$htmltag\b\s*[^>]*>~";
66 $strippedtext = preg_replace($tagpair, '', $strippedtext);
0d24b17a 67 }
060e0294 68
8d6fb0c6
TH
69 $textarray = array();
70 preg_match_all($this->htmltstarttagsandattributes, $strippedtext, $textarray);
0d24b17a 71 if ($textarray[0]) {
8d6fb0c6 72 return $this->allowed_tags_message($textarray[0][0]);
0d24b17a 73 }
060e0294 74
8d6fb0c6 75 preg_match_all($this->htmltclosetags, $strippedtext, $textarray);
0d24b17a 76 if ($textarray[0]) {
8d6fb0c6 77 return $this->allowed_tags_message($textarray[0][0]);
0d24b17a 78 }
060e0294 79
8d6fb0c6
TH
80 return '';
81 }
82
8bc1d28b
EM
83 /**
84 * Returns a message indicating what tags are allowed.
85 *
86 * @param string $badtag The disallowed tag that was supplied
87 * @return string Message indicating what tags are allowed
88 */
8d6fb0c6
TH
89 private function allowed_tags_message($badtag) {
90 $a = new stdClass();
91 $a->tag = htmlspecialchars($badtag);
92 $a->allowed = $this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
93 if ($a->allowed) {
94 return get_string('tagsnotallowed', 'qtype_gapselect', $a);
95 } else {
96 return get_string('tagsnotallowedatall', 'qtype_gapselect', $a);
97 }
0d24b17a
TH
98 }
99
8bc1d28b
EM
100 /**
101 * Returns a prinatble list of allowed HTML tags.
102 *
103 * @param array $allowedhtmltags An array for tag strings that are allowed
104 * @return string A printable list of tags
105 */
0d24b17a 106 private function get_list_of_printable_allowed_tags($allowedhtmltags) {
8d6fb0c6 107 $allowedtaglist = array();
0d24b17a 108 foreach ($allowedhtmltags as $htmltag) {
8d6fb0c6 109 $allowedtaglist[] = htmlspecialchars('<' . $htmltag . '>');
0d24b17a 110 }
8d6fb0c6 111 return implode(', ', $allowedtaglist);
0d24b17a
TH
112 }
113
114 /**
115 * definition_inner adds all specific fields to the form.
116 * @param object $mform (the form being built).
117 */
046d8165 118 protected function definition_inner($mform) {
0d24b17a
TH
119 global $CFG;
120
8d6fb0c6 121 // Add the answer (choice) fields to the form.
0d24b17a
TH
122 $this->definition_answer_choice($mform);
123
124 $this->add_combined_feedback_fields(true);
125 $this->add_interactive_settings(true, true);
126 }
127
8bc1d28b
EM
128 /**
129 * Defines form elements for answer choices.
130 *
131 * @param object $mform The Moodle form object being built
132 */
0d24b17a 133 protected function definition_answer_choice(&$mform) {
721ef2ea 134 $mform->addElement('header', 'choicehdr', get_string('choices', 'qtype_gapselect'));
4c489e0e 135 $mform->setExpanded('choicehdr', 1);
0d24b17a 136
721ef2ea 137 $mform->addElement('checkbox', 'shuffleanswers', get_string('shuffle', 'qtype_gapselect'));
0d24b17a
TH
138 $mform->setDefault('shuffleanswers', 0);
139
140 $textboxgroup = array();
721ef2ea
TH
141 $textboxgroup[] = $mform->createElement('group', 'choices',
142 get_string('choicex', 'qtype_gapselect'), $this->choice_group($mform));
0d24b17a
TH
143
144 if (isset($this->question->options)) {
145 $countanswers = count($this->question->options->answers);
146 } else {
147 $countanswers = 0;
148 }
149
150 if ($this->question->formoptions->repeatelements) {
721ef2ea 151 $defaultstartnumbers = QUESTION_NUMANS_START * 2;
060e0294
TH
152 $repeatsatstart = max($defaultstartnumbers, QUESTION_NUMANS_START,
153 $countanswers + QUESTION_NUMANS_ADD);
0d24b17a
TH
154 } else {
155 $repeatsatstart = $countanswers;
156 }
157
158 $repeatedoptions = $this->repeated_options();
159 $mform->setType('answer', PARAM_RAW);
060e0294
TH
160 $this->repeat_elements($textboxgroup, $repeatsatstart, $repeatedoptions,
161 'noanswers', 'addanswers', QUESTION_NUMANS_ADD,
1f2cae4c 162 get_string('addmorechoiceblanks', 'qtype_gapselect'), true);
0d24b17a
TH
163 }
164
4889d6ac
HN
165 /**
166 * Return how many different groups of choices there should be.
167 *
168 * @return int the maximum group number.
169 */
170 function get_maximum_choice_group_number() {
171 return 8;
172 }
173
8bc1d28b
EM
174 /**
175 * Creates an array with elements for a choice group.
176 *
177 * @param object $mform The Moodle form we are working with
4889d6ac 178 * @param int $maxgroup The number of max group generate element select.
8bc1d28b
EM
179 * @return array Array for form elements
180 */
721ef2ea
TH
181 protected function choice_group($mform) {
182 $options = array();
4889d6ac 183 for ($i = 1; $i <= $this->get_maximum_choice_group_number(); $i += 1) {
721ef2ea
TH
184 $options[$i] = $i;
185 }
186 $grouparray = array();
060e0294 187 $grouparray[] = $mform->createElement('text', 'answer',
647d9373 188 get_string('answer', 'qtype_gapselect'), array('size' => 30, 'class' => 'tweakcss'));
060e0294
TH
189 $grouparray[] = $mform->createElement('select', 'choicegroup',
190 get_string('group', 'qtype_gapselect'), $options);
721ef2ea
TH
191 return $grouparray;
192 }
0d24b17a 193
8bc1d28b
EM
194 /**
195 * Returns an array for form repeat options.
196 *
197 * @return array Array of repeate options
198 */
721ef2ea
TH
199 protected function repeated_options() {
200 $repeatedoptions = array();
201 $repeatedoptions['choicegroup']['default'] = '1';
09ab0166 202 $repeatedoptions['choices[answer]']['type'] = PARAM_RAW;
721ef2ea
TH
203 return $repeatedoptions;
204 }
0d24b17a 205
721ef2ea
TH
206 public function data_preprocessing($question) {
207 $question = parent::data_preprocessing($question);
208 $question = $this->data_preprocessing_combined_feedback($question, true);
209 $question = $this->data_preprocessing_hints($question, true, true);
210
211 $question = $this->data_preprocessing_answers($question, true);
212 if (!empty($question->options->answers)) {
213 $key = 0;
214 foreach ($question->options->answers as $answer) {
215 $question = $this->data_preprocessing_choice($question, $answer, $key);
216 $key++;
0d24b17a 217 }
721ef2ea 218 }
0d24b17a 219
721ef2ea
TH
220 if (!empty($question->options)) {
221 $question->shuffleanswers = $question->options->shuffleanswers;
0d24b17a 222 }
721ef2ea
TH
223
224 return $question;
225 }
226
227 protected function data_preprocessing_choice($question, $answer, $key) {
721ef2ea
TH
228 $question->choices[$key]['answer'] = $answer->answer;
229 $question->choices[$key]['choicegroup'] = $answer->feedback;
230 return $question;
0d24b17a
TH
231 }
232
233 public function validation($data, $files) {
234 $errors = parent::validation($data, $files);
235 $questiontext = $data['questiontext'];
236 $choices = $data['choices'];
237
8d6fb0c6 238 // Check the whether the slots are valid.
721ef2ea 239 $errorsinquestiontext = $this->validate_slots($questiontext['text'], $choices);
0d24b17a
TH
240 if ($errorsinquestiontext) {
241 $errors['questiontext'] = $errorsinquestiontext;
242 }
243 foreach ($choices as $key => $choice) {
244 $answer = $choice['answer'];
245
647d9373 246 // Check whether the HTML tags are allowed tags.
8d6fb0c6
TH
247 $tagerror = $this->get_illegal_tag_error($answer);
248 if ($tagerror) {
249 $errors['choices['.$key.']'] = $tagerror;
0d24b17a
TH
250 }
251 }
252 return $errors;
253 }
254
8bc1d28b
EM
255 /**
256 * Finds errors in question slots.
257 *
258 * @param string $questiontext The question text
259 * @param array $choices Question choices
260 * @return string|bool Error message or false if no errors
261 */
0d24b17a
TH
262 private function validate_slots($questiontext, $choices) {
263 $error = 'Please check the Question text: ';
264 if (!$questiontext) {
a719f4d2 265 return get_string('errorquestiontextblank', 'qtype_gapselect');
0d24b17a
TH
266 }
267
268 $matches = array();
060e0294 269 preg_match_all($this->squarebracketsregex, $questiontext, $matches);
0d24b17a
TH
270 $slots = $matches[0];
271
272 if (!$slots) {
a719f4d2 273 return get_string('errornoslots', 'qtype_gapselect');
0d24b17a
TH
274 }
275
a719f4d2 276 $cleanedslots = array();
0d24b17a
TH
277 foreach ($slots as $slot) {
278 // The 2 is for'[[' and 4 is for '[[]]'.
647d9373 279 $cleanedslots[] = substr($slot, 2, (strlen($slot) - 4));
0d24b17a 280 }
a719f4d2 281 $slots = $cleanedslots;
0d24b17a 282
0d24b17a
TH
283 $found = false;
284 foreach ($slots as $slot) {
285 $found = false;
286 foreach ($choices as $key => $choice) {
287 if ($slot == $key + 1) {
a719f4d2
TH
288 if ($choice['answer'] === '') {
289 return get_string('errorblankchoice', 'qtype_gapselect',
290 html_writer::tag('b', $slot));
0d24b17a
TH
291 }
292 $found = true;
293 break;
294 }
295 }
296 if (!$found) {
a719f4d2
TH
297 return get_string('errormissingchoice', 'qtype_gapselect',
298 html_writer::tag('b', $slot));
0d24b17a
TH
299 }
300 }
301 return false;
302 }
721ef2ea 303
046d8165 304 public function qtype() {
0d24b17a
TH
305 return '';
306 }
8d6fb0c6 307}