Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}