PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/question/type/gapselect/edit_form_base.php

https://bitbucket.org/moodle/moodle
PHP | 308 lines | 181 code | 40 blank | 87 comment | 20 complexity | eb6feb39d887c8a2e0197653d264a392 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
  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. * Base class for editing question types like this one.
  18. *
  19. * @package qtype_gapselect
  20. * @copyright 2011 The Open University
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. /**
  25. * Elements embedded in question text editing form definition.
  26. *
  27. * @copyright 2011 The Open University
  28. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29. */
  30. class qtype_gapselect_edit_form_base extends question_edit_form {
  31. /** @var array of HTML tags allowed in choices / drag boxes. */
  32. protected $allowedhtmltags = array(
  33. 'sub',
  34. 'sup',
  35. 'b',
  36. 'i',
  37. 'em',
  38. 'strong',
  39. 'span',
  40. );
  41. /** @var string regex to match HTML open tags. */
  42. private $htmltstarttagsandattributes = '~<\s*\w+\b[^>]*>~';
  43. /** @var string regex to match HTML close tags or br. */
  44. private $htmltclosetags = '~<\s*/\s*\w+\b[^>]*>~';
  45. /** @var string regex to select text like [[cat]] (including the square brackets). */
  46. private $squarebracketsregex = '/\[\[[^]]*?\]\]/';
  47. /**
  48. * Vaidate some input to make sure it does not contain any tags other than
  49. * $this->allowedhtmltags.
  50. * @param string $text the input to validate.
  51. * @return string any validation errors.
  52. */
  53. protected function get_illegal_tag_error($text) {
  54. // Remove legal tags.
  55. $strippedtext = $text;
  56. foreach ($this->allowedhtmltags as $htmltag) {
  57. $tagpair = "~<\s*/?\s*$htmltag\b\s*[^>]*>~";
  58. $strippedtext = preg_replace($tagpair, '', $strippedtext);
  59. }
  60. $textarray = array();
  61. preg_match_all($this->htmltstarttagsandattributes, $strippedtext, $textarray);
  62. if ($textarray[0]) {
  63. return $this->allowed_tags_message($textarray[0][0]);
  64. }
  65. preg_match_all($this->htmltclosetags, $strippedtext, $textarray);
  66. if ($textarray[0]) {
  67. return $this->allowed_tags_message($textarray[0][0]);
  68. }
  69. return '';
  70. }
  71. /**
  72. * Returns a message indicating what tags are allowed.
  73. *
  74. * @param string $badtag The disallowed tag that was supplied
  75. * @return string Message indicating what tags are allowed
  76. */
  77. private function allowed_tags_message($badtag) {
  78. $a = new stdClass();
  79. $a->tag = htmlspecialchars($badtag);
  80. $a->allowed = $this->get_list_of_printable_allowed_tags($this->allowedhtmltags);
  81. if ($a->allowed) {
  82. return get_string('tagsnotallowed', 'qtype_gapselect', $a);
  83. } else {
  84. return get_string('tagsnotallowedatall', 'qtype_gapselect', $a);
  85. }
  86. }
  87. /**
  88. * Returns a prinatble list of allowed HTML tags.
  89. *
  90. * @param array $allowedhtmltags An array for tag strings that are allowed
  91. * @return string A printable list of tags
  92. */
  93. private function get_list_of_printable_allowed_tags($allowedhtmltags) {
  94. $allowedtaglist = array();
  95. foreach ($allowedhtmltags as $htmltag) {
  96. $allowedtaglist[] = htmlspecialchars('<' . $htmltag . '>');
  97. }
  98. return implode(', ', $allowedtaglist);
  99. }
  100. /**
  101. * definition_inner adds all specific fields to the form.
  102. * @param object $mform (the form being built).
  103. */
  104. protected function definition_inner($mform) {
  105. global $CFG;
  106. // Add the answer (choice) fields to the form.
  107. $this->definition_answer_choice($mform);
  108. $this->add_combined_feedback_fields(true);
  109. $this->add_interactive_settings(true, true);
  110. }
  111. /**
  112. * Defines form elements for answer choices.
  113. *
  114. * @param object $mform The Moodle form object being built
  115. */
  116. protected function definition_answer_choice(&$mform) {
  117. $mform->addElement('header', 'choicehdr', get_string('choices', 'qtype_gapselect'));
  118. $mform->setExpanded('choicehdr', 1);
  119. $mform->addElement('checkbox', 'shuffleanswers', get_string('shuffle', 'qtype_gapselect'));
  120. $mform->setDefault('shuffleanswers', $this->get_default_value('shuffleanswers', 0));
  121. $textboxgroup = array();
  122. $textboxgroup[] = $mform->createElement('group', 'choices',
  123. get_string('choicex', 'qtype_gapselect'), $this->choice_group($mform));
  124. if (isset($this->question->options)) {
  125. $countanswers = count($this->question->options->answers);
  126. } else {
  127. $countanswers = 0;
  128. }
  129. if ($this->question->formoptions->repeatelements) {
  130. $defaultstartnumbers = QUESTION_NUMANS_START * 2;
  131. $repeatsatstart = max($defaultstartnumbers, QUESTION_NUMANS_START,
  132. $countanswers + QUESTION_NUMANS_ADD);
  133. } else {
  134. $repeatsatstart = $countanswers;
  135. }
  136. $repeatedoptions = $this->repeated_options();
  137. $mform->setType('answer', PARAM_RAW);
  138. $this->repeat_elements($textboxgroup, $repeatsatstart, $repeatedoptions,
  139. 'noanswers', 'addanswers', QUESTION_NUMANS_ADD,
  140. get_string('addmorechoiceblanks', 'qtype_gapselect'), true);
  141. }
  142. /**
  143. * Return how many different groups of choices there should be.
  144. *
  145. * @return int the maximum group number.
  146. */
  147. function get_maximum_choice_group_number() {
  148. return 8;
  149. }
  150. /**
  151. * Creates an array with elements for a choice group.
  152. *
  153. * @param object $mform The Moodle form we are working with
  154. * @param int $maxgroup The number of max group generate element select.
  155. * @return array Array for form elements
  156. */
  157. protected function choice_group($mform) {
  158. $options = array();
  159. for ($i = 1; $i <= $this->get_maximum_choice_group_number(); $i += 1) {
  160. $options[$i] = question_utils::int_to_letter($i);
  161. }
  162. $grouparray = array();
  163. $grouparray[] = $mform->createElement('text', 'answer',
  164. get_string('answer', 'qtype_gapselect'), array('size' => 30, 'class' => 'tweakcss'));
  165. $grouparray[] = $mform->createElement('select', 'choicegroup',
  166. get_string('group', 'qtype_gapselect'), $options);
  167. return $grouparray;
  168. }
  169. /**
  170. * Returns an array for form repeat options.
  171. *
  172. * @return array Array of repeate options
  173. */
  174. protected function repeated_options() {
  175. $repeatedoptions = array();
  176. $repeatedoptions['choicegroup']['default'] = '1';
  177. $repeatedoptions['choices[answer]']['type'] = PARAM_RAW;
  178. return $repeatedoptions;
  179. }
  180. public function data_preprocessing($question) {
  181. $question = parent::data_preprocessing($question);
  182. $question = $this->data_preprocessing_combined_feedback($question, true);
  183. $question = $this->data_preprocessing_hints($question, true, true);
  184. $question = $this->data_preprocessing_answers($question, true);
  185. if (!empty($question->options->answers)) {
  186. $key = 0;
  187. foreach ($question->options->answers as $answer) {
  188. $question = $this->data_preprocessing_choice($question, $answer, $key);
  189. $key++;
  190. }
  191. }
  192. if (!empty($question->options)) {
  193. $question->shuffleanswers = $question->options->shuffleanswers;
  194. }
  195. return $question;
  196. }
  197. protected function data_preprocessing_choice($question, $answer, $key) {
  198. $question->choices[$key]['answer'] = $answer->answer;
  199. $question->choices[$key]['choicegroup'] = $answer->feedback;
  200. return $question;
  201. }
  202. public function validation($data, $files) {
  203. $errors = parent::validation($data, $files);
  204. $questiontext = $data['questiontext'];
  205. $choices = $data['choices'];
  206. // Check the whether the slots are valid.
  207. $errorsinquestiontext = $this->validate_slots($questiontext['text'], $choices);
  208. if ($errorsinquestiontext) {
  209. $errors['questiontext'] = $errorsinquestiontext;
  210. }
  211. foreach ($choices as $key => $choice) {
  212. $answer = $choice['answer'];
  213. // Check whether the HTML tags are allowed tags.
  214. $tagerror = $this->get_illegal_tag_error($answer);
  215. if ($tagerror) {
  216. $errors['choices['.$key.']'] = $tagerror;
  217. }
  218. }
  219. return $errors;
  220. }
  221. /**
  222. * Finds errors in question slots.
  223. *
  224. * @param string $questiontext The question text
  225. * @param array $choices Question choices
  226. * @return string|bool Error message or false if no errors
  227. */
  228. private function validate_slots($questiontext, $choices) {
  229. $error = 'Please check the Question text: ';
  230. if (!$questiontext) {
  231. return get_string('errorquestiontextblank', 'qtype_gapselect');
  232. }
  233. $matches = array();
  234. preg_match_all($this->squarebracketsregex, $questiontext, $matches);
  235. $slots = $matches[0];
  236. if (!$slots) {
  237. return get_string('errornoslots', 'qtype_gapselect');
  238. }
  239. $cleanedslots = array();
  240. foreach ($slots as $slot) {
  241. // The 2 is for'[[' and 4 is for '[[]]'.
  242. $cleanedslots[] = substr($slot, 2, (strlen($slot) - 4));
  243. }
  244. $slots = $cleanedslots;
  245. $found = false;
  246. foreach ($slots as $slot) {
  247. $found = false;
  248. foreach ($choices as $key => $choice) {
  249. if ($slot == $key + 1) {
  250. if ($choice['answer'] === '') {
  251. return get_string('errorblankchoice', 'qtype_gapselect',
  252. html_writer::tag('b', $slot));
  253. }
  254. $found = true;
  255. break;
  256. }
  257. }
  258. if (!$found) {
  259. return get_string('errormissingchoice', 'qtype_gapselect',
  260. html_writer::tag('b', $slot));
  261. }
  262. }
  263. return false;
  264. }
  265. public function qtype() {
  266. return '';
  267. }
  268. }