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