Merge branch 'MDL-70248-310' of https://github.com/HuongNV13/moodle into MOODLE_310_S...
[moodle.git] / question / type / gapselect / edit_form_base.php
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/>.
17 /**
18  * Base class for editing question types like this one.
19  *
20  * @package    qtype_gapselect
21  * @copyright  2011 The Open University
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die();
28 /**
29  * Elements embedded in question text editing form definition.
30  *
31  * @copyright  2011 The Open University
32  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33  */
34 class qtype_gapselect_edit_form_base extends question_edit_form {
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         'span',
45     );
47     /** @var string regex to match HTML open tags. */
48     private $htmltstarttagsandattributes = '~<\s*\w+\b[^>]*>~';
50     /** @var string regex to match HTML close tags or br. */
51     private $htmltclosetags = '~<\s*/\s*\w+\b[^>]*>~';
53     /** @var string regex to select text like [[cat]] (including the square brackets). */
54     private $squarebracketsregex = '/\[\[[^]]*?\]\]/';
56     /**
57      * Vaidate some input to make sure it does not contain any tags other than
58      * $this->allowedhtmltags.
59      * @param string $text the input to validate.
60      * @return string any validation errors.
61      */
62     protected function get_illegal_tag_error($text) {
63         // Remove legal tags.
64         $strippedtext = $text;
65         foreach ($this->allowedhtmltags as $htmltag) {
66             $tagpair = "~<\s*/?\s*$htmltag\b\s*[^>]*>~";
67             $strippedtext = preg_replace($tagpair, '', $strippedtext);
68         }
70         $textarray = array();
71         preg_match_all($this->htmltstarttagsandattributes, $strippedtext, $textarray);
72         if ($textarray[0]) {
73             return $this->allowed_tags_message($textarray[0][0]);
74         }
76         preg_match_all($this->htmltclosetags, $strippedtext, $textarray);
77         if ($textarray[0]) {
78             return $this->allowed_tags_message($textarray[0][0]);
79         }
81         return '';
82     }
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      */
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         }
99     }
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      */
107     private function get_list_of_printable_allowed_tags($allowedhtmltags) {
108         $allowedtaglist = array();
109         foreach ($allowedhtmltags as $htmltag) {
110             $allowedtaglist[] = htmlspecialchars('<' . $htmltag . '>');
111         }
112         return implode(', ', $allowedtaglist);
113     }
115     /**
116      * definition_inner adds all specific fields to the form.
117      * @param object $mform (the form being built).
118      */
119     protected function definition_inner($mform) {
120         global $CFG;
122         // Add the answer (choice) fields to the form.
123         $this->definition_answer_choice($mform);
125         $this->add_combined_feedback_fields(true);
126         $this->add_interactive_settings(true, true);
127     }
129     /**
130      * Defines form elements for answer choices.
131      *
132      * @param object $mform The Moodle form object being built
133      */
134     protected function definition_answer_choice(&$mform) {
135         $mform->addElement('header', 'choicehdr', get_string('choices', 'qtype_gapselect'));
136         $mform->setExpanded('choicehdr', 1);
138         $mform->addElement('checkbox', 'shuffleanswers', get_string('shuffle', 'qtype_gapselect'));
139         $mform->setDefault('shuffleanswers', 0);
141         $textboxgroup = array();
142         $textboxgroup[] = $mform->createElement('group', 'choices',
143                 get_string('choicex', 'qtype_gapselect'), $this->choice_group($mform));
145         if (isset($this->question->options)) {
146             $countanswers = count($this->question->options->answers);
147         } else {
148             $countanswers = 0;
149         }
151         if ($this->question->formoptions->repeatelements) {
152             $defaultstartnumbers = QUESTION_NUMANS_START * 2;
153             $repeatsatstart = max($defaultstartnumbers, QUESTION_NUMANS_START,
154                     $countanswers + QUESTION_NUMANS_ADD);
155         } else {
156             $repeatsatstart = $countanswers;
157         }
159         $repeatedoptions = $this->repeated_options();
160         $mform->setType('answer', PARAM_RAW);
161         $this->repeat_elements($textboxgroup, $repeatsatstart, $repeatedoptions,
162                 'noanswers', 'addanswers', QUESTION_NUMANS_ADD,
163                 get_string('addmorechoiceblanks', 'qtype_gapselect'), true);
164     }
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     }
175     /**
176      * Creates an array with elements for a choice group.
177      *
178      * @param object $mform The Moodle form we are working with
179      * @param int $maxgroup The number of max group generate element select.
180      * @return array Array for form elements
181      */
182     protected function choice_group($mform) {
183         $options = array();
184         for ($i = 1; $i <= $this->get_maximum_choice_group_number(); $i += 1) {
185             $options[$i] = question_utils::int_to_letter($i);
186         }
187         $grouparray = array();
188         $grouparray[] = $mform->createElement('text', 'answer',
189                 get_string('answer', 'qtype_gapselect'), array('size' => 30, 'class' => 'tweakcss'));
190         $grouparray[] = $mform->createElement('select', 'choicegroup',
191                 get_string('group', 'qtype_gapselect'), $options);
192         return $grouparray;
193     }
195     /**
196      * Returns an array for form repeat options.
197      *
198      * @return array Array of repeate options
199      */
200     protected function repeated_options() {
201         $repeatedoptions = array();
202         $repeatedoptions['choicegroup']['default'] = '1';
203         $repeatedoptions['choices[answer]']['type'] = PARAM_RAW;
204         return $repeatedoptions;
205     }
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);
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++;
218             }
219         }
221         if (!empty($question->options)) {
222             $question->shuffleanswers = $question->options->shuffleanswers;
223         }
225         return $question;
226     }
228     protected function data_preprocessing_choice($question, $answer, $key) {
229         $question->choices[$key]['answer'] = $answer->answer;
230         $question->choices[$key]['choicegroup'] = $answer->feedback;
231         return $question;
232     }
234     public function validation($data, $files) {
235         $errors = parent::validation($data, $files);
236         $questiontext = $data['questiontext'];
237         $choices = $data['choices'];
239         // Check the whether the slots are valid.
240         $errorsinquestiontext = $this->validate_slots($questiontext['text'], $choices);
241         if ($errorsinquestiontext) {
242             $errors['questiontext'] = $errorsinquestiontext;
243         }
244         foreach ($choices as $key => $choice) {
245             $answer = $choice['answer'];
247             // Check whether the HTML tags are allowed tags.
248             $tagerror = $this->get_illegal_tag_error($answer);
249             if ($tagerror) {
250                 $errors['choices['.$key.']'] = $tagerror;
251             }
252         }
253         return $errors;
254     }
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      */
263     private function validate_slots($questiontext, $choices) {
264         $error = 'Please check the Question text: ';
265         if (!$questiontext) {
266             return get_string('errorquestiontextblank', 'qtype_gapselect');
267         }
269         $matches = array();
270         preg_match_all($this->squarebracketsregex, $questiontext, $matches);
271         $slots = $matches[0];
273         if (!$slots) {
274             return get_string('errornoslots', 'qtype_gapselect');
275         }
277         $cleanedslots = array();
278         foreach ($slots as $slot) {
279             // The 2 is for'[[' and 4 is for '[[]]'.
280             $cleanedslots[] = substr($slot, 2, (strlen($slot) - 4));
281         }
282         $slots = $cleanedslots;
284         $found = false;
285         foreach ($slots as $slot) {
286             $found = false;
287             foreach ($choices as $key => $choice) {
288                 if ($slot == $key + 1) {
289                     if ($choice['answer'] === '') {
290                         return get_string('errorblankchoice', 'qtype_gapselect',
291                                 html_writer::tag('b', $slot));
292                     }
293                     $found = true;
294                     break;
295                 }
296             }
297             if (!$found) {
298                 return get_string('errormissingchoice', 'qtype_gapselect',
299                         html_writer::tag('b', $slot));
300             }
301         }
302         return false;
303     }
305     public function qtype() {
306         return '';
307     }