ÿØÿà JFIF    ÿÛ „  ( %"1!%)+...383,7(-.+  -+++--++++---+-+-----+---------------+---+-++7-----ÿÀ  ß â" ÿÄ     ÿÄ H    !1AQaq"‘¡2B±ÁÑð#R“Ò Tbr‚²á3csƒ’ÂñDS¢³$CÿÄ   ÿÄ %  !1AQa"23‘ÿÚ   ? ôÿ ¨pŸªáÿ —åYõõ\?àÒü©ŠÄï¨pŸªáÿ —åYõõ\?àÓü©ŠÄá 0Ÿªáÿ Ÿå[úƒ ú®ði~TÁbqÐ8OÕpÿ ƒOò¤Oè`–RÂáœá™êi€ßÉ< FtŸI“öÌ8úDf´°å}“¾œ6  öFá°y¥jñÇh†ˆ¢ã/ÃÐ:ªcÈ "Y¡ðÑl>ÿ ”ÏËte:qž\oäŠe÷󲍷˜HT4&ÿ ÓÐü6ö®¿øþßèô Ÿ•7Ñi’•j|“ñì>b…þS?*Óôÿ ÓÐü*h¥£ír¶ü UãS炟[AÐaè[ûª•õ&õj?†Éö+EzP—WeÒírJFt ‘BŒ†Ï‡%#tE Øz ¥OÛ«!1›üä±Í™%ºÍãö]°î(–:@<‹ŒÊö×òÆt¦ãº+‡¦%ÌÁ²h´OƒJŒtMÜ>ÀÜÊw3Y´•牋4ǍýʏTì>œú=Íwhyë,¾Ôò×õ¿ßÊa»«þˆѪQ|%6ž™A õ%:øj<>É—ÿ Å_ˆCbõ¥š±ý¯Ýƒï…¶|RëócÍf溪“t.СøTÿ *Ä¿-{†çàczůŽ_–^XþŒ±miB[X±d 1,é”zEù»& î9gœf™9Ð'.;—™i}!ôšåîqêÛ٤ёý£½ÆA–àôe"A$˝Úsäÿ ÷Û #°xŸëí(l »ý3—¥5m! rt`†0~'j2(]S¦¦kv,ÚÇ l¦øJA£Šƒ J3E8ÙiŽ:cÉžúeZ°€¯\®kÖ(79«Ž:¯X”¾³Š&¡* ….‰Ž(ÜíŸ2¥ª‡×Hi²TF¤ò[¨íÈRëÉ䢍mgÑ.Ÿ<öäS0í„ǹÁU´f#Vß;Õ–…P@3ío<ä-±»Ž.L|kªÀê›fÂ6@»eu‚|ÓaÞÆŸ…¨ááå>åŠ?cKü6ùTÍÆ”†sĤÚ;H2RÚ†õ\Ö·Ÿn'¾ ñ#ºI¤Å´%çÁ­‚â7›‹qT3Iï¨ÖÚ5I7Ë!ÅOóŸ¶øÝñØôת¦$Tcö‘[«Ö³šÒ';Aþ ¸èíg A2Z"i¸vdÄ÷.iõ®§)¿]¤À†–‡É&ä{V¶iŽ”.Ó×Õÿ û?h¬Mt–íª[ÿ Ñÿ ÌV(í}=ibÔ¡›¥¢±b Lô¥‡piη_Z<‡z§èŒ)iÖwiÇ 2hÙ3·=’d÷8éŽ1¦¸c¤µ€7›7Ø ð\á)} ¹fËí›pAÃL%âc2 í§æQz¿;T8sæ°qø)QFMð‰XŒÂ±N¢aF¨…8¯!U  Z©RÊ ÖPVÄÀÍin™Ì-GˆªÅËŠ›•zË}º±ŽÍFò¹}Uw×#ä5B¤{î}Ð<ÙD é©¤&‡ïDbàÁôMÁ.. /** * Base class for editing question types like this one. * * @package qtype_gapselect * @copyright 2011 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Elements embedded in question text editing form definition. * * @copyright 2011 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class qtype_gapselect_edit_form_base extends question_edit_form { /** @var array of HTML tags allowed in choices / drag boxes. */ protected $allowedhtmltags = array( 'sub', 'sup', 'b', 'i', 'em', 'strong', 'span', ); /** @var string regex to match HTML open tags. */ private $htmltstarttagsandattributes = '~<\s*\w+\b[^>]*>~'; /** @var string regex to match HTML close tags or br. */ private $htmltclosetags = '~<\s*/\s*\w+\b[^>]*>~'; /** @var string regex to select text like [[cat]] (including the square brackets). */ private $squarebracketsregex = '/\[\[[^]]*?\]\]/'; /** * Vaidate some input to make sure it does not contain any tags other than * $this->allowedhtmltags. * @param string $text the input to validate. * @return string any validation errors. */ protected function get_illegal_tag_error($text) { // Remove legal tags. $strippedtext = $text; foreach ($this->allowedhtmltags as $htmltag) { $tagpair = "~<\s*/?\s*$htmltag\b\s*[^>]*>~"; $strippedtext = preg_replace($tagpair, '', $strippedtext); } $textarray = array(); preg_match_all($this->htmltstarttagsandattributes, $strippedtext, $textarray); if ($textarray[0]) { return $this->allowed_tags_message($textarray[0][0]); } preg_match_all($this->htmltclosetags, $strippedtext, $textarray); if ($textarray[0]) { return $this->allowed_tags_message($textarray[0][0]); } return ''; } /** * Returns a message indicating what tags are allowed. * * @param string $badtag The disallowed tag that was supplied * @return string Message indicating what tags are allowed */ private function allowed_tags_message($badtag) { $a = new stdClass(); $a->tag = htmlspecialchars($badtag, ENT_COMPAT); $a->allowed = $this->get_list_of_printable_allowed_tags($this->allowedhtmltags); if ($a->allowed) { return get_string('tagsnotallowed', 'qtype_gapselect', $a); } else { return get_string('tagsnotallowedatall', 'qtype_gapselect', $a); } } /** * Returns a prinatble list of allowed HTML tags. * * @param array $allowedhtmltags An array for tag strings that are allowed * @return string A printable list of tags */ private function get_list_of_printable_allowed_tags($allowedhtmltags) { $allowedtaglist = array(); foreach ($allowedhtmltags as $htmltag) { $allowedtaglist[] = htmlspecialchars('<' . $htmltag . '>', ENT_COMPAT); } return implode(', ', $allowedtaglist); } /** * definition_inner adds all specific fields to the form. * @param object $mform (the form being built). */ protected function definition_inner($mform) { global $CFG; // Add the answer (choice) fields to the form. $this->definition_answer_choice($mform); $this->add_combined_feedback_fields(true); $this->add_interactive_settings(true, true); } /** * Defines form elements for answer choices. * * @param object $mform The Moodle form object being built */ protected function definition_answer_choice(&$mform) { $mform->addElement('header', 'choicehdr', get_string('choices', 'qtype_gapselect')); $mform->setExpanded('choicehdr', 1); $mform->addElement('checkbox', 'shuffleanswers', get_string('shuffle', 'qtype_gapselect')); $mform->setDefault('shuffleanswers', $this->get_default_value('shuffleanswers', 0)); $textboxgroup = array(); $textboxgroup[] = $mform->createElement('group', 'choices', get_string('choicex', 'qtype_gapselect'), $this->choice_group($mform)); if (!empty($this->question->options->answers)) { $repeatsatstart = count($this->question->options->answers); } else { $repeatsatstart = QUESTION_NUMANS_ADD * 2; } $repeatedoptions = $this->repeated_options(); $mform->setType('answer', PARAM_RAW); $this->repeat_elements($textboxgroup, $repeatsatstart, $repeatedoptions, 'noanswers', 'addanswers', QUESTION_NUMANS_ADD, get_string('addmorechoiceblanks', 'qtype_gapselect'), true); } /** * Return how many different groups of choices there should be. * * @return int the maximum group number. */ function get_maximum_choice_group_number() { return 8; } /** * Creates an array with elements for a choice group. * * @param object $mform The Moodle form we are working with * @param int $maxgroup The number of max group generate element select. * @return array Array for form elements */ protected function choice_group($mform) { $options = array(); for ($i = 1; $i <= $this->get_maximum_choice_group_number(); $i += 1) { $options[$i] = question_utils::int_to_letter($i); } $grouparray = array(); $grouparray[] = $mform->createElement('text', 'answer', get_string('answer', 'qtype_gapselect'), array('size' => 30, 'class' => 'tweakcss')); $grouparray[] = $mform->createElement('select', 'choicegroup', get_string('group', 'qtype_gapselect'), $options); return $grouparray; } /** * Returns an array for form repeat options. * * @return array Array of repeate options */ protected function repeated_options() { $repeatedoptions = array(); $repeatedoptions['choicegroup']['default'] = '1'; $repeatedoptions['choices[answer]']['type'] = PARAM_RAW; return $repeatedoptions; } public function data_preprocessing($question) { $question = parent::data_preprocessing($question); $question = $this->data_preprocessing_combined_feedback($question, true); $question = $this->data_preprocessing_hints($question, true, true); $question = $this->data_preprocessing_answers($question, true); if (!empty($question->options->answers)) { $key = 0; foreach ($question->options->answers as $answer) { $question = $this->data_preprocessing_choice($question, $answer, $key); $key++; } } if (!empty($question->options)) { $question->shuffleanswers = $question->options->shuffleanswers; } return $question; } protected function data_preprocessing_choice($question, $answer, $key) { $question->choices[$key]['answer'] = $answer->answer; $question->choices[$key]['choicegroup'] = $answer->feedback; return $question; } public function validation($data, $files) { $errors = parent::validation($data, $files); $questiontext = $data['questiontext']; $choices = $data['choices']; // Check the whether the slots are valid. $errorsinquestiontext = $this->validate_slots($questiontext['text'], $choices); if ($errorsinquestiontext) { $errors['questiontext'] = $errorsinquestiontext; } foreach ($choices as $key => $choice) { $answer = $choice['answer']; // Check whether the HTML tags are allowed tags. $tagerror = $this->get_illegal_tag_error($answer); if ($tagerror) { $errors['choices['.$key.']'] = $tagerror; } } return $errors; } /** * Finds errors in question slots. * * @param string $questiontext The question text * @param array $choices Question choices * @return string|bool Error message or false if no errors */ private function validate_slots($questiontext, $choices) { $error = 'Please check the Question text: '; if (!$questiontext) { return get_string('errorquestiontextblank', 'qtype_gapselect'); } $matches = array(); preg_match_all($this->squarebracketsregex, $questiontext, $matches); $slots = $matches[0]; if (!$slots) { return get_string('errornoslots', 'qtype_gapselect'); } $cleanedslots = array(); foreach ($slots as $slot) { // The 2 is for'[[' and 4 is for '[[]]'. $cleanedslots[] = substr($slot, 2, (strlen($slot) - 4)); } $slots = $cleanedslots; foreach ($slots as $slot) { $found = false; foreach ($choices as $key => $choice) { if ($slot == $key + 1) { if ($choice['answer'] === '') { return get_string('errorblankchoice', 'qtype_gapselect', html_writer::tag('b', $slot)); } $found = true; break; } } if (!$found) { return get_string('errormissingchoice', 'qtype_gapselect', html_writer::tag('b', $slot)); } } return $this->extra_slot_validation($slots, $choices) ?? false; } public function qtype() { return ''; } /** * Finds more errors in question slots. * * @param array $slots The question text * @param array $choices Question choices * @return string|null Error message or false if no errors */ protected function extra_slot_validation(array $slots, array $choices): ?string { return null; } }