See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
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/>. 16 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 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 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 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 ); 46 47 /** @var string regex to match HTML open tags. */ 48 private $htmltstarttagsandattributes = '~<\s*\w+\b[^>]*>~'; 49 50 /** @var string regex to match HTML close tags or br. */ 51 private $htmltclosetags = '~<\s*/\s*\w+\b[^>]*>~'; 52 53 /** @var string regex to select text like [[cat]] (including the square brackets). */ 54 private $squarebracketsregex = '/\[\[[^]]*?\]\]/'; 55 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 } 69 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 } 75 76 preg_match_all($this->htmltclosetags, $strippedtext, $textarray); 77 if ($textarray[0]) { 78 return $this->allowed_tags_message($textarray[0][0]); 79 } 80 81 return ''; 82 } 83 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, ENT_COMPAT); 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 } 100 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 . '>', ENT_COMPAT); 111 } 112 return implode(', ', $allowedtaglist); 113 } 114 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; 121 122 // Add the answer (choice) fields to the form. 123 $this->definition_answer_choice($mform); 124 125 $this->add_combined_feedback_fields(true); 126 $this->add_interactive_settings(true, true); 127 } 128 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); 137 138 $mform->addElement('checkbox', 'shuffleanswers', get_string('shuffle', 'qtype_gapselect')); 139 $mform->setDefault('shuffleanswers', $this->get_default_value('shuffleanswers', 0)); 140 141 $textboxgroup = array(); 142 $textboxgroup[] = $mform->createElement('group', 'choices', 143 get_string('choicex', 'qtype_gapselect'), $this->choice_group($mform)); 144 145 if (!empty($this->question->options->answers)) { 146 $repeatsatstart = count($this->question->options->answers); 147 } else { 148 $repeatsatstart = QUESTION_NUMANS_ADD * 2; 149 } 150 151 $repeatedoptions = $this->repeated_options(); 152 $mform->setType('answer', PARAM_RAW); 153 $this->repeat_elements($textboxgroup, $repeatsatstart, $repeatedoptions, 154 'noanswers', 'addanswers', QUESTION_NUMANS_ADD, 155 get_string('addmorechoiceblanks', 'qtype_gapselect'), true); 156 } 157 158 /** 159 * Return how many different groups of choices there should be. 160 * 161 * @return int the maximum group number. 162 */ 163 function get_maximum_choice_group_number() { 164 return 8; 165 } 166 167 /** 168 * Creates an array with elements for a choice group. 169 * 170 * @param object $mform The Moodle form we are working with 171 * @param int $maxgroup The number of max group generate element select. 172 * @return array Array for form elements 173 */ 174 protected function choice_group($mform) { 175 $options = array(); 176 for ($i = 1; $i <= $this->get_maximum_choice_group_number(); $i += 1) { 177 $options[$i] = question_utils::int_to_letter($i); 178 } 179 $grouparray = array(); 180 $grouparray[] = $mform->createElement('text', 'answer', 181 get_string('answer', 'qtype_gapselect'), array('size' => 30, 'class' => 'tweakcss')); 182 $grouparray[] = $mform->createElement('select', 'choicegroup', 183 get_string('group', 'qtype_gapselect'), $options); 184 return $grouparray; 185 } 186 187 /** 188 * Returns an array for form repeat options. 189 * 190 * @return array Array of repeate options 191 */ 192 protected function repeated_options() { 193 $repeatedoptions = array(); 194 $repeatedoptions['choicegroup']['default'] = '1'; 195 $repeatedoptions['choices[answer]']['type'] = PARAM_RAW; 196 return $repeatedoptions; 197 } 198 199 public function data_preprocessing($question) { 200 $question = parent::data_preprocessing($question); 201 $question = $this->data_preprocessing_combined_feedback($question, true); 202 $question = $this->data_preprocessing_hints($question, true, true); 203 204 $question = $this->data_preprocessing_answers($question, true); 205 if (!empty($question->options->answers)) { 206 $key = 0; 207 foreach ($question->options->answers as $answer) { 208 $question = $this->data_preprocessing_choice($question, $answer, $key); 209 $key++; 210 } 211 } 212 213 if (!empty($question->options)) { 214 $question->shuffleanswers = $question->options->shuffleanswers; 215 } 216 217 return $question; 218 } 219 220 protected function data_preprocessing_choice($question, $answer, $key) { 221 $question->choices[$key]['answer'] = $answer->answer; 222 $question->choices[$key]['choicegroup'] = $answer->feedback; 223 return $question; 224 } 225 226 public function validation($data, $files) { 227 $errors = parent::validation($data, $files); 228 $questiontext = $data['questiontext']; 229 $choices = $data['choices']; 230 231 // Check the whether the slots are valid. 232 $errorsinquestiontext = $this->validate_slots($questiontext['text'], $choices); 233 if ($errorsinquestiontext) { 234 $errors['questiontext'] = $errorsinquestiontext; 235 } 236 foreach ($choices as $key => $choice) { 237 $answer = $choice['answer']; 238 239 // Check whether the HTML tags are allowed tags. 240 $tagerror = $this->get_illegal_tag_error($answer); 241 if ($tagerror) { 242 $errors['choices['.$key.']'] = $tagerror; 243 } 244 } 245 return $errors; 246 } 247 248 /** 249 * Finds errors in question slots. 250 * 251 * @param string $questiontext The question text 252 * @param array $choices Question choices 253 * @return string|bool Error message or false if no errors 254 */ 255 private function validate_slots($questiontext, $choices) { 256 $error = 'Please check the Question text: '; 257 if (!$questiontext) { 258 return get_string('errorquestiontextblank', 'qtype_gapselect'); 259 } 260 261 $matches = array(); 262 preg_match_all($this->squarebracketsregex, $questiontext, $matches); 263 $slots = $matches[0]; 264 265 if (!$slots) { 266 return get_string('errornoslots', 'qtype_gapselect'); 267 } 268 269 $cleanedslots = array(); 270 foreach ($slots as $slot) { 271 // The 2 is for'[[' and 4 is for '[[]]'. 272 $cleanedslots[] = substr($slot, 2, (strlen($slot) - 4)); 273 } 274 $slots = $cleanedslots; 275 276 foreach ($slots as $slot) { 277 $found = false; 278 foreach ($choices as $key => $choice) { 279 if ($slot == $key + 1) { 280 if ($choice['answer'] === '') { 281 return get_string('errorblankchoice', 'qtype_gapselect', 282 html_writer::tag('b', $slot)); 283 } 284 $found = true; 285 break; 286 } 287 } 288 if (!$found) { 289 return get_string('errormissingchoice', 'qtype_gapselect', 290 html_writer::tag('b', $slot)); 291 } 292 } 293 return $this->extra_slot_validation($slots, $choices) ?? false; 294 } 295 296 public function qtype() { 297 return ''; 298 } 299 300 /** 301 * Finds more errors in question slots. 302 * 303 * @param array $slots The question text 304 * @param array $choices Question choices 305 * @return string|null Error message or false if no errors 306 */ 307 protected function extra_slot_validation(array $slots, array $choices): ?string { 308 return null; 309 } 310 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body