MDL-47494 gapselect: Add @package and GPL boiler-plate to files in /question.
[moodle.git] / question / type / gapselect / edit_form_base.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Base class for editing question types like this one.
20  *
21  * @package qtype
22  * @subpackage gapselect
23  * @copyright 2011 The Open University
24  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
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 {
35     const MAX_GROUPS = 8;
37     /** @var array of HTML tags allowed in choices / drag boxes. */
38     protected $allowedhtmltags = array(
39         'sub',
40         'sup',
41         'b',
42         'i',
43         'em',
44         'strong'
45     );
47     /** @var string regex to match HTML open tags. */
48     private $htmltstarttagsandattributes = '/<\s*\w.*?>/';
50     /** @var string regex to match HTML close tags or br. */
51     private $htmltclosetags = '~<\s*/\s*\w\s*.*?>|<\s*br\s*>~';
53     /** @var string regex to select text like [[cat]] (including the square brackets). */
54     private $squareBracketsRegex = '/\[\[[^]]*?\]\]/';
56     private function get_html_tags($text) {
57         $textarray = array();
58         foreach ($this->allowedhtmltags as $htmltag) {
59             $tagpair = "/<\s*\/?\s*$htmltag\s*.*?>/";
60             preg_match_all($tagpair, $text, $textarray);
61             if ($textarray[0]) {
62                 return $textarray[0];
63             }
64         }
65         preg_match_all($this->htmltstarttagsandattributes, $text, $textarray);
66         if ($textarray[0]) {
67             $tag = htmlspecialchars($textarray[0][0]);
68             $allowedtaglist = $this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
69             return $tag . " is not allowed (only $allowedtaglist and corresponsing closing tags are allowed)";
70         }
71         preg_match_all($this->htmltclosetags, $text, $textarray);
72         if ($textarray[0]) {
73             $tag = htmlspecialchars($textarray[0][0]);
74             $allowedtaglist=$this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
75             return $tag . " is not allowed HTML tag! (only $allowedtaglist and corresponsing closing tags are allowed)";
76         }
77         return false;
78     }
80     private function get_list_of_printable_allowed_tags($allowedhtmltags) {
81         $allowedtaglist = null;
82         foreach ($allowedhtmltags as $htmltag) {
83             $allowedtaglist .= htmlspecialchars('<'.$htmltag.'>') . ', ';
84         }
85         return $allowedtaglist;
86     }
88     /**
89      * definition_inner adds all specific fields to the form.
90      * @param object $mform (the form being built).
91      */
92     function definition_inner($mform) {
93         global $CFG;
95         //add the answer (choice) fields to the form
96         $this->definition_answer_choice($mform);
98         $this->add_combined_feedback_fields(true);
99         $this->add_interactive_settings(true, true);
100     }
102     protected function definition_answer_choice(&$mform) {
103         $mform->addElement('header', 'choicehdr', get_string('choices', 'qtype_gapselect'));
105         $mform->addElement('checkbox', 'shuffleanswers', get_string('shuffle', 'qtype_gapselect'));
106         $mform->setDefault('shuffleanswers', 0);
108         $textboxgroup = array();
109         $textboxgroup[] = $mform->createElement('group', 'choices',
110                 get_string('choicex', 'qtype_gapselect'), $this->choice_group($mform));
112         if (isset($this->question->options)) {
113             $countanswers = count($this->question->options->answers);
114         } else {
115             $countanswers = 0;
116         }
118         if ($this->question->formoptions->repeatelements) {
119             $defaultstartnumbers = QUESTION_NUMANS_START * 2;
120             $repeatsatstart = max($defaultstartnumbers, QUESTION_NUMANS_START, $countanswers + QUESTION_NUMANS_ADD);
121         } else {
122             $repeatsatstart = $countanswers;
123         }
125         $repeatedoptions = $this->repeated_options();
126         $mform->setType('answer', PARAM_RAW);
127         $this->repeat_elements($textboxgroup, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers', QUESTION_NUMANS_ADD, get_string('addmorechoiceblanks', 'qtype_gapselect'));
128     }
130     protected function choice_group($mform) {
131         $options = array();
132         for ($i = 1; $i <= self::MAX_GROUPS; $i += 1) {
133             $options[$i] = $i;
134         }
135         $grouparray = array();
136         $grouparray[] = $mform->createElement('text', 'answer', get_string('answer', 'qtype_gapselect'), array('size'=>30, 'class'=>'tweakcss'));
137         $grouparray[] = $mform->createElement('static', '', '',' '.get_string('group', 'qtype_gapselect').' ');
138         $grouparray[] = $mform->createElement('select', 'choicegroup', get_string('group', 'qtype_gapselect'), $options);
139         return $grouparray;
140     }
142     protected function repeated_options() {
143         $repeatedoptions = array();
144         $repeatedoptions['choicegroup']['default'] = '1';
145         return $repeatedoptions;
146     }
148     public function data_preprocessing($question) {
149         $question = parent::data_preprocessing($question);
150         $question = $this->data_preprocessing_combined_feedback($question, true);
151         $question = $this->data_preprocessing_hints($question, true, true);
153         $question = $this->data_preprocessing_answers($question, true);
154         if (!empty($question->options->answers)) {
155             $key = 0;
156             foreach ($question->options->answers as $answer) {
157                 $question = $this->data_preprocessing_choice($question, $answer, $key);
158                 $key++;
159             }
160         }
162         if (!empty($question->options)) {
163             $question->shuffleanswers = $question->options->shuffleanswers;
164         }
166         return $question;
167     }
169     protected function data_preprocessing_choice($question, $answer, $key) {
170         // See comment in data_preprocessing_answers.
171         unset($this->_form->_defaultValues['choices[$key][choicegroup]']);
172         $question->choices[$key]['answer'] = $answer->answer;
173         $question->choices[$key]['choicegroup'] = $answer->feedback;
174         return $question;
175     }
177     public function validation($data, $files) {
178         $errors = parent::validation($data, $files);
179         $questiontext = $data['questiontext'];
180         $choices = $data['choices'];
182         //check the whether the slots are valid
183         $errorsinquestiontext = $this->validate_slots($questiontext['text'], $choices);
184         if ($errorsinquestiontext) {
185             $errors['questiontext'] = $errorsinquestiontext;
186         }
187         foreach ($choices as $key => $choice) {
188             $answer = $choice['answer'];
190             //check whether the html-tags are allowed tags
191             $validtags = $this->get_html_tags($answer);
192             if (is_array($validtags)) {
193                 continue;
194             }
195             if ($validtags) {
196                 $errors['choices['.$key.']'] = $validtags;
197             }
198         }
199         return $errors;
200     }
202     private function validate_slots($questiontext, $choices) {
203         $error = 'Please check the Question text: ';
204         if (!$questiontext) {
205             return $error . 'The question text is empty!';
206         }
208         $matches = array();
209         preg_match_all($this->squareBracketsRegex, $questiontext, $matches);
210         $slots = $matches[0];
212         if (!$slots) {
213             return $error . 'The question text is not in the correct format!';
214         }
216         $output = array();
217         foreach ($slots as $slot) {
218             // The 2 is for'[[' and 4 is for '[[]]'.
219             $output[] = substr($slot, 2, (strlen($slot)-4));
220         }
222         $slots = $output;
223         $found = false;
224         foreach ($slots as $slot) {
225             $found = false;
226             foreach ($choices as $key => $choice) {
227                 if ($slot == $key + 1) {
228                     if (!$choice['answer']) {
229                         return " Please check Choices: The choice <b>$slot</b> empty.";
230                     }
231                     $found = true;
232                     break;
233                 }
234             }
235             if (!$found) {
236                 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!";
237             }
238         }
239         return false;
240     }
242     function qtype() {
243         return '';
244     }