Differences Between: [Versions 39 and 402]
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 * Short answer question definition class. 19 * 20 * @package qtype 21 * @subpackage shortanswer 22 * @copyright 2009 The Open University 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 require_once($CFG->dirroot . '/question/type/questionbase.php'); 30 31 /** 32 * Represents a short answer question. 33 * 34 * @copyright 2009 The Open University 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class qtype_shortanswer_question extends question_graded_by_strategy 38 implements question_response_answer_comparer { 39 /** @var boolean whether answers should be graded case-sensitively. */ 40 public $usecase; 41 /** @var array of question_answer. */ 42 public $answers = array(); 43 44 public function __construct() { 45 parent::__construct(new question_first_matching_answer_grading_strategy($this)); 46 } 47 48 public function get_expected_data() { 49 return array('answer' => PARAM_RAW_TRIMMED); 50 } 51 52 public function summarise_response(array $response) { 53 if (isset($response['answer'])) { 54 return $response['answer']; 55 } else { 56 return null; 57 } 58 } 59 60 public function un_summarise_response(string $summary) { 61 if (!empty($summary)) { 62 return ['answer' => $summary]; 63 } else { 64 return []; 65 } 66 } 67 68 public function is_complete_response(array $response) { 69 return array_key_exists('answer', $response) && 70 ($response['answer'] || $response['answer'] === '0'); 71 } 72 73 public function get_validation_error(array $response) { 74 if ($this->is_gradable_response($response)) { 75 return ''; 76 } 77 return get_string('pleaseenterananswer', 'qtype_shortanswer'); 78 } 79 80 public function is_same_response(array $prevresponse, array $newresponse) { 81 return question_utils::arrays_same_at_key_missing_is_blank( 82 $prevresponse, $newresponse, 'answer'); 83 } 84 85 public function get_answers() { 86 return $this->answers; 87 } 88 89 public function compare_response_with_answer(array $response, question_answer $answer) { 90 if (!array_key_exists('answer', $response) || is_null($response['answer'])) { 91 return false; 92 } 93 94 return self::compare_string_with_wildcard( 95 $response['answer'], $answer->answer, !$this->usecase); 96 } 97 98 public static function compare_string_with_wildcard($string, $pattern, $ignorecase) { 99 100 // Normalise any non-canonical UTF-8 characters before we start. 101 $pattern = self::safe_normalize($pattern); 102 $string = self::safe_normalize($string); 103 104 // Break the string on non-escaped runs of asterisks. 105 // ** is equivalent to *, but people were doing that, and with many *s it breaks preg. 106 $bits = preg_split('/(?<!\\\\)\*+/', $pattern); 107 108 // Escape regexp special characters in the bits. 109 $escapedbits = array(); 110 foreach ($bits as $bit) { 111 $escapedbits[] = preg_quote(str_replace('\*', '*', $bit), '|'); 112 } 113 // Put it back together to make the regexp. 114 $regexp = '|^' . implode('.*', $escapedbits) . '$|u'; 115 116 // Make the match insensitive if requested to. 117 if ($ignorecase) { 118 $regexp .= 'i'; 119 } 120 121 return preg_match($regexp, trim($string)); 122 } 123 124 /** 125 * Normalise a UTf-8 string to FORM_C, avoiding the pitfalls in PHP's 126 * normalizer_normalize function. 127 * @param string $string the input string. 128 * @return string the normalised string. 129 */ 130 protected static function safe_normalize($string) { 131 if ($string === '') { 132 return ''; 133 } 134 135 if (!function_exists('normalizer_normalize')) { 136 return $string; 137 } 138 139 $normalised = normalizer_normalize($string, Normalizer::FORM_C); 140 if (is_null($normalised)) { 141 // An error occurred in normalizer_normalize, but we have no idea what. 142 debugging('Failed to normalise string: ' . $string, DEBUG_DEVELOPER); 143 return $string; // Return the original string, since it is the best we have. 144 } 145 146 return $normalised; 147 } 148 149 public function get_correct_response() { 150 $response = parent::get_correct_response(); 151 if ($response) { 152 $response['answer'] = $this->clean_response($response['answer']); 153 } 154 return $response; 155 } 156 157 public function clean_response($answer) { 158 // Break the string on non-escaped asterisks. 159 $bits = preg_split('/(?<!\\\\)\*/', $answer); 160 161 // Unescape *s in the bits. 162 $cleanbits = array(); 163 foreach ($bits as $bit) { 164 $cleanbits[] = str_replace('\*', '*', $bit); 165 } 166 167 // Put it back together with spaces to look nice. 168 return trim(implode(' ', $cleanbits)); 169 } 170 171 public function check_file_access($qa, $options, $component, $filearea, 172 $args, $forcedownload) { 173 if ($component == 'question' && $filearea == 'answerfeedback') { 174 $currentanswer = $qa->get_last_qt_var('answer'); 175 $answer = $this->get_matching_answer(array('answer' => $currentanswer)); 176 $answerid = reset($args); // Itemid is answer id. 177 return $options->feedback && $answer && $answerid == $answer->id; 178 179 } else if ($component == 'question' && $filearea == 'hint') { 180 return $this->check_hint_file_access($qa, $options, $args); 181 182 } else { 183 return parent::check_file_access($qa, $options, $component, $filearea, 184 $args, $forcedownload); 185 } 186 } 187 188 /** 189 * Return the question settings that define this question as structured data. 190 * 191 * @param question_attempt $qa the current attempt for which we are exporting the settings. 192 * @param question_display_options $options the question display options which say which aspects of the question 193 * should be visible. 194 * @return mixed structure representing the question settings. In web services, this will be JSON-encoded. 195 */ 196 public function get_question_definition_for_external_rendering(question_attempt $qa, question_display_options $options) { 197 // No need to return anything, external clients do not need additional information for rendering this question type. 198 return null; 199 } 200 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body