a9ea3329143d5195bdcb35c22a5bf940aa2420a3
[moodle.git] / question / type / gapselect / edit_form_base.php
1 <?php
4 /**
5  * Elements embedded in question text editing form definition.
6  *
7  * @copyright 2011 The Open University
8  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
9  */
10 class qtype_gapselect_edit_form_base extends question_edit_form {
11     const MAX_GROUPS = 8;
13     /** @var array of HTML tags allowed in choices / drag boxes. */
14     protected $allowedhtmltags = array(
15         'sub',
16         'sup',
17         'b',
18         'i',
19         'em',
20         'strong'
21     );
23     /** @var string regex to match HTML open tags. */
24     private $htmltstarttagsandattributes = '/<\s*\w.*?>/';
26     /** @var string regex to match HTML close tags or br. */
27     private $htmltclosetags = '~<\s*/\s*\w\s*.*?>|<\s*br\s*>~';
29     /** @var string regex to select text like [[cat]] (including the square brackets). */
30     private $squareBracketsRegex = '/\[\[[^]]*?\]\]/';
32     private function get_html_tags($text) {
33         $textarray = array();
34         foreach ($this->allowedhtmltags as $htmltag) {
35             $tagpair = "/<\s*\/?\s*$htmltag\s*.*?>/";
36             preg_match_all($tagpair, $text, $textarray);
37             if ($textarray[0]) {
38                 return $textarray[0];
39             }
40         }
41         preg_match_all($this->htmltstarttagsandattributes, $text, $textarray);
42         if ($textarray[0]) {
43             $tag = htmlspecialchars($textarray[0][0]);
44             $allowedtaglist = $this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
45             return $tag . " is not allowed (only $allowedtaglist and corresponsing closing tags are allowed)";
46         }
47         preg_match_all($this->htmltclosetags, $text, $textarray);
48         if ($textarray[0]) {
49             $tag = htmlspecialchars($textarray[0][0]);
50             $allowedtaglist=$this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
51             return $tag . " is not allowed HTML tag! (only $allowedtaglist and corresponsing closing tags are allowed)";
52         }
53         return false;
54     }
56     private function get_list_of_printable_allowed_tags($allowedhtmltags) {
57         $allowedtaglist = null;
58         foreach ($allowedhtmltags as $htmltag) {
59             $allowedtaglist .= htmlspecialchars('<'.$htmltag.'>') . ', ';
60         }
61         return $allowedtaglist;
62     }
64     /**
65      * definition_inner adds all specific fields to the form.
66      * @param object $mform (the form being built).
67      */
68     function definition_inner($mform) {
69         global $CFG;
71         //add the answer (choice) fields to the form
72         $this->definition_answer_choice($mform);
74         $this->add_combined_feedback_fields(true);
75         $this->add_interactive_settings(true, true);
76     }
78     protected function definition_answer_choice(&$mform) {
79         $mform->addElement('header', 'choicehdr', get_string('choices', 'qtype_gapselect'));
81         $mform->addElement('checkbox', 'shuffleanswers', get_string('shuffle', 'qtype_gapselect'));
82         $mform->setDefault('shuffleanswers', 0);
84         $textboxgroup = array();
85         $textboxgroup[] = $mform->createElement('group', 'choices',
86                 get_string('choicex', 'qtype_gapselect'), $this->choice_group($mform));
88         if (isset($this->question->options)) {
89             $countanswers = count($this->question->options->answers);
90         } else {
91             $countanswers = 0;
92         }
94         if ($this->question->formoptions->repeatelements) {
95             $defaultstartnumbers = QUESTION_NUMANS_START * 2;
96             $repeatsatstart = max($defaultstartnumbers, QUESTION_NUMANS_START, $countanswers + QUESTION_NUMANS_ADD);
97         } else {
98             $repeatsatstart = $countanswers;
99         }
101         $repeatedoptions = $this->repeated_options();
102         $mform->setType('answer', PARAM_RAW);
103         $this->repeat_elements($textboxgroup, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers', QUESTION_NUMANS_ADD, get_string('addmorechoiceblanks', 'qtype_gapselect'));
104     }
106     protected function choice_group($mform) {
107         $options = array();
108         for ($i = 1; $i <= self::MAX_GROUPS; $i += 1) {
109             $options[$i] = $i;
110         }
111         $grouparray = array();
112         $grouparray[] = $mform->createElement('text', 'answer', get_string('answer', 'qtype_gapselect'), array('size'=>30, 'class'=>'tweakcss'));
113         $grouparray[] = $mform->createElement('static', '', '',' '.get_string('group', 'qtype_gapselect').' ');
114         $grouparray[] = $mform->createElement('select', 'choicegroup', get_string('group', 'qtype_gapselect'), $options);
115         return $grouparray;
116     }
118     protected function repeated_options() {
119         $repeatedoptions = array();
120         $repeatedoptions['choicegroup']['default'] = '1';
121         return $repeatedoptions;
122     }
124     public function data_preprocessing($question) {
125         $question = parent::data_preprocessing($question);
126         $question = $this->data_preprocessing_combined_feedback($question, true);
127         $question = $this->data_preprocessing_hints($question, true, true);
129         $question = $this->data_preprocessing_answers($question, true);
130         if (!empty($question->options->answers)) {
131             $key = 0;
132             foreach ($question->options->answers as $answer) {
133                 $question = $this->data_preprocessing_choice($question, $answer, $key);
134                 $key++;
135             }
136         }
138         if (!empty($question->options)) {
139             $question->shuffleanswers = $question->options->shuffleanswers;
140         }
142         return $question;
143     }
145     protected function data_preprocessing_choice($question, $answer, $key) {
146         // See comment in data_preprocessing_answers.
147         unset($this->_form->_defaultValues['choices[$key][choicegroup]']);
148         $question->choices[$key]['answer'] = $answer->answer;
149         $question->choices[$key]['choicegroup'] = $answer->feedback;
150         return $question;
151     }
153     public function validation($data, $files) {
154         $errors = parent::validation($data, $files);
155         $questiontext = $data['questiontext'];
156         $choices = $data['choices'];
158         //check the whether the slots are valid
159         $errorsinquestiontext = $this->validate_slots($questiontext['text'], $choices);
160         if ($errorsinquestiontext) {
161             $errors['questiontext'] = $errorsinquestiontext;
162         }
163         foreach ($choices as $key => $choice) {
164             $answer = $choice['answer'];
166             //check whether the html-tags are allowed tags
167             $validtags = $this->get_html_tags($answer);
168             if (is_array($validtags)) {
169                 continue;
170             }
171             if ($validtags) {
172                 $errors['choices['.$key.']'] = $validtags;
173             }
174         }
175         return $errors;
176     }
178     private function validate_slots($questiontext, $choices) {
179         $error = 'Please check the Question text: ';
180         if (!$questiontext) {
181             return $error . 'The question text is empty!';
182         }
184         $matches = array();
185         preg_match_all($this->squareBracketsRegex, $questiontext, $matches);
186         $slots = $matches[0];
188         if (!$slots) {
189             return $error . 'The question text is not in the correct format!';
190         }
192         $output = array();
193         foreach ($slots as $slot) {
194             // The 2 is for'[[' and 4 is for '[[]]'.
195             $output[] = substr($slot, 2, (strlen($slot)-4));
196         }
198         $slots = $output;
199         $found = false;
200         foreach ($slots as $slot) {
201             $found = false;
202             foreach ($choices as $key => $choice) {
203                 if ($slot == $key + 1) {
204                     if (!$choice['answer']) {
205                         return " Please check Choices: The choice <b>$slot</b> empty.";
206                     }
207                     $found = true;
208                     break;
209                 }
210             }
211             if (!$found) {
212                 return $error . "<b>$slot</b> was not found in Choices! (only the choice numbers that exist in choices are allowed to be used a place holders!";
213             }
214         }
215         return false;
216     }
218     function qtype() {
219         return '';
220     }