5e7626bd4872c41b7d3907c785882bba18e684cf
[moodle.git] / question / type / shortanswer / question.php
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/>.
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  */
27 defined('MOODLE_INTERNAL') || die();
30 /**
31  * Represents a short answer question.
32  *
33  * @copyright  2009 The Open University
34  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class qtype_shortanswer_question extends question_graded_by_strategy
37         implements question_response_answer_comparer {
38     /** @var boolean whether answers should be graded case-sensitively. */
39     public $usecase;
40     /** @var array of question_answer. */
41     public $answers = array();
43     public function __construct() {
44         parent::__construct(new question_first_matching_answer_grading_strategy($this));
45     }
47     public function get_expected_data() {
48         return array('answer' => PARAM_RAW_TRIMMED);
49     }
51     public function summarise_response(array $response) {
52         if (isset($response['answer'])) {
53             return $response['answer'];
54         } else {
55             return null;
56         }
57     }
59     public function is_complete_response(array $response) {
60         return array_key_exists('answer', $response) &&
61                 ($response['answer'] || $response['answer'] === '0');
62     }
64     public function get_validation_error(array $response) {
65         if ($this->is_gradable_response($response)) {
66             return '';
67         }
68         return get_string('pleaseenterananswer', 'qtype_shortanswer');
69     }
71     public function is_same_response(array $prevresponse, array $newresponse) {
72         return question_utils::arrays_same_at_key_missing_is_blank(
73                 $prevresponse, $newresponse, 'answer');
74     }
76     public function get_answers() {
77         return $this->answers;
78     }
80     public function compare_response_with_answer(array $response, question_answer $answer) {
81         if (!array_key_exists('answer', $response) || is_null($response['answer'])) {
82             return false;
83         }
85         return self::compare_string_with_wildcard(
86                 $response['answer'], $answer->answer, !$this->usecase);
87     }
89     public static function compare_string_with_wildcard($string, $pattern, $ignorecase) {
90         // Break the string on non-escaped asterisks.
91         $bits = preg_split('/(?<!\\\\)\*/', $pattern);
92         // Escape regexp special characters in the bits.
93         $excapedbits = array();
94         foreach ($bits as $bit) {
95             $excapedbits[] = preg_quote(str_replace('\*', '*', $bit));
96         }
97         // Put it back together to make the regexp.
98         $regexp = '|^' . implode('.*', $excapedbits) . '$|u';
100         // Make the match insensitive if requested to.
101         if ($ignorecase) {
102             $regexp .= 'i';
103         }
105         return preg_match($regexp, trim($string));
106     }
108     public function get_correct_response() {
109         $response = parent::get_correct_response();
110         if ($response) {
111             $response['answer'] = $this->clean_response($response['answer']);
112         }
113         return $response;
114     }
116     public function clean_response($answer) {
117         // Break the string on non-escaped asterisks.
118         $bits = preg_split('/(?<!\\\\)\*/', $answer);
120         // Unescape *s in the bits.
121         $cleanbits = array();
122         foreach ($bits as $bit) {
123             $cleanbits[] = str_replace('\*', '*', $bit);
124         }
126         // Put it back together with spaces to look nice.
127         return trim(implode(' ', $cleanbits));
128     }
130     public function check_file_access($qa, $options, $component, $filearea,
131             $args, $forcedownload) {
132         if ($component == 'question' && $filearea == 'answerfeedback') {
133             $currentanswer = $qa->get_last_qt_var('answer');
134             $answer = $this->get_matching_answer(array('answer' => $currentanswer));
135             $answerid = reset($args); // itemid is answer id.
136             return $options->feedback && $answer && $answerid == $answer->id;
138         } else if ($component == 'question' && $filearea == 'hint') {
139             return $this->check_hint_file_access($qa, $options, $args);
141         } else {
142             return parent::check_file_access($qa, $options, $component, $filearea,
143                     $args, $forcedownload);
144         }
145     }