21d5afe985ec5c3b7f819fe51c0104027140de60
[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 defined('MOODLE_INTERNAL') || die();
31 /**
32  * Elements embedded in question text editing form definition.
33  *
34  * @copyright  2011 The Open University
35  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36  */
37 class qtype_gapselect_edit_form_base extends question_edit_form {
38     const MAX_GROUPS = 8;
40     /** @var array of HTML tags allowed in choices / drag boxes. */
41     protected $allowedhtmltags = array(
42         'sub',
43         'sup',
44         'b',
45         'i',
46         'em',
47         'strong'
48     );
50     /** @var string regex to match HTML open tags. */
51     private $htmltstarttagsandattributes = '/<\s*\w.*?>/';
53     /** @var string regex to match HTML close tags or br. */
54     private $htmltclosetags = '~<\s*/\s*\w\s*.*?>|<\s*br\s*>~';
56     /** @var string regex to select text like [[cat]] (including the square brackets). */
57     private $squareBracketsRegex = '/\[\[[^]]*?\]\]/';
59     private function get_html_tags($text) {
60         $textarray = array();
61         foreach ($this->allowedhtmltags as $htmltag) {
62             $tagpair = "/<\s*\/?\s*$htmltag\s*.*?>/";
63             preg_match_all($tagpair, $text, $textarray);
64             if ($textarray[0]) {
65                 return $textarray[0];
66             }
67         }
68         preg_match_all($this->htmltstarttagsandattributes, $text, $textarray);
69         if ($textarray[0]) {
70             $tag = htmlspecialchars($textarray[0][0]);
71             $allowedtaglist = $this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
72             return $tag . " is not allowed (only $allowedtaglist and corresponsing closing tags are allowed)";
73         }
74         preg_match_all($this->htmltclosetags, $text, $textarray);
75         if ($textarray[0]) {
76             $tag = htmlspecialchars($textarray[0][0]);
77             $allowedtaglist=$this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
78             return $tag . " is not allowed HTML tag! (only $allowedtaglist and corresponsing closing tags are allowed)";
79         }
80         return false;
81     }
83     private function get_list_of_printable_allowed_tags($allowedhtmltags) {
84         $allowedtaglist = null;
85         foreach ($allowedhtmltags as $htmltag) {
86             $allowedtaglist .= htmlspecialchars('<'.$htmltag.'>') . ', ';
87         }
88         return $allowedtaglist;
89     }
91     /**
92      * definition_inner adds all specific fields to the form.
93      * @param object $mform (the form being built).
94      */
95     function definition_inner($mform) {
96         global $CFG;
98         //add the answer (choice) fields to the form
99         $this->definition_answer_choice($mform);
101         $this->add_combined_feedback_fields(true);
102         $this->add_interactive_settings(true, true);
103     }
105     protected function definition_answer_choice(&$mform) {
106         $mform->addElement('header', 'choicehdr', get_string('choices', 'qtype_gapselect'));
108         $mform->addElement('checkbox', 'shuffleanswers', get_string('shuffle', 'qtype_gapselect'));
109         $mform->setDefault('shuffleanswers', 0);
111         $textboxgroup = array();
112         $textboxgroup[] = $mform->createElement('group', 'choices',
113                 get_string('choicex', 'qtype_gapselect'), $this->choice_group($mform));
115         if (isset($this->question->options)) {
116             $countanswers = count($this->question->options->answers);
117         } else {
118             $countanswers = 0;
119         }
121         if ($this->question->formoptions->repeatelements) {
122             $defaultstartnumbers = QUESTION_NUMANS_START * 2;
123             $repeatsatstart = max($defaultstartnumbers, QUESTION_NUMANS_START, $countanswers + QUESTION_NUMANS_ADD);
124         } else {
125             $repeatsatstart = $countanswers;
126         }
128         $repeatedoptions = $this->repeated_options();
129         $mform->setType('answer', PARAM_RAW);
130         $this->repeat_elements($textboxgroup, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers', QUESTION_NUMANS_ADD, get_string('addmorechoiceblanks', 'qtype_gapselect'));
131     }
133     protected function choice_group($mform) {
134         $options = array();
135         for ($i = 1; $i <= self::MAX_GROUPS; $i += 1) {
136             $options[$i] = $i;
137         }
138         $grouparray = array();
139         $grouparray[] = $mform->createElement('text', 'answer', get_string('answer', 'qtype_gapselect'), array('size'=>30, 'class'=>'tweakcss'));
140         $grouparray[] = $mform->createElement('static', '', '',' '.get_string('group', 'qtype_gapselect').' ');
141         $grouparray[] = $mform->createElement('select', 'choicegroup', get_string('group', 'qtype_gapselect'), $options);
142         return $grouparray;
143     }
145     protected function repeated_options() {
146         $repeatedoptions = array();
147         $repeatedoptions['choicegroup']['default'] = '1';
148         return $repeatedoptions;
149     }
151     public function data_preprocessing($question) {
152         $question = parent::data_preprocessing($question);
153         $question = $this->data_preprocessing_combined_feedback($question, true);
154         $question = $this->data_preprocessing_hints($question, true, true);
156         $question = $this->data_preprocessing_answers($question, true);
157         if (!empty($question->options->answers)) {
158             $key = 0;
159             foreach ($question->options->answers as $answer) {
160                 $question = $this->data_preprocessing_choice($question, $answer, $key);
161                 $key++;
162             }
163         }
165         if (!empty($question->options)) {
166             $question->shuffleanswers = $question->options->shuffleanswers;
167         }
169         return $question;
170     }
172     protected function data_preprocessing_choice($question, $answer, $key) {
173         // See comment in data_preprocessing_answers.
174         unset($this->_form->_defaultValues['choices[$key][choicegroup]']);
175         $question->choices[$key]['answer'] = $answer->answer;
176         $question->choices[$key]['choicegroup'] = $answer->feedback;
177         return $question;
178     }
180     public function validation($data, $files) {
181         $errors = parent::validation($data, $files);
182         $questiontext = $data['questiontext'];
183         $choices = $data['choices'];
185         //check the whether the slots are valid
186         $errorsinquestiontext = $this->validate_slots($questiontext['text'], $choices);
187         if ($errorsinquestiontext) {
188             $errors['questiontext'] = $errorsinquestiontext;
189         }
190         foreach ($choices as $key => $choice) {
191             $answer = $choice['answer'];
193             //check whether the html-tags are allowed tags
194             $validtags = $this->get_html_tags($answer);
195             if (is_array($validtags)) {
196                 continue;
197             }
198             if ($validtags) {
199                 $errors['choices['.$key.']'] = $validtags;
200             }
201         }
202         return $errors;
203     }
205     private function validate_slots($questiontext, $choices) {
206         $error = 'Please check the Question text: ';
207         if (!$questiontext) {
208             return $error . 'The question text is empty!';
209         }
211         $matches = array();
212         preg_match_all($this->squareBracketsRegex, $questiontext, $matches);
213         $slots = $matches[0];
215         if (!$slots) {
216             return $error . 'The question text is not in the correct format!';
217         }
219         $output = array();
220         foreach ($slots as $slot) {
221             // The 2 is for'[[' and 4 is for '[[]]'.
222             $output[] = substr($slot, 2, (strlen($slot)-4));
223         }
225         $slots = $output;
226         $found = false;
227         foreach ($slots as $slot) {
228             $found = false;
229             foreach ($choices as $key => $choice) {
230                 if ($slot == $key + 1) {
231                     if (!$choice['answer']) {
232                         return " Please check Choices: The choice <b>$slot</b> empty.";
233                     }
234                     $found = true;
235                     break;
236                 }
237             }
238             if (!$found) {
239                 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!";
240             }
241         }
242         return false;
243     }
245     function qtype() {
246         return '';
247     }