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
 * Essay question definition class.
19
 *
20
 * @package    qtype
21
 * @subpackage essay
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 an essay 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_essay_question extends question_with_responses {
38
 
39
    public $responseformat;
40
 
41
    /** @var int Indicates whether an inline response is required ('0') or optional ('1')  */
42
    public $responserequired;
43
 
44
    public $responsefieldlines;
45
 
46
    /** @var int indicates whether the minimum number of words required */
47
    public $minwordlimit;
48
 
49
    /** @var int indicates whether the maximum number of words required */
50
    public $maxwordlimit;
51
 
52
    public $attachments;
53
 
54
    /** @var int maximum file size in bytes */
55
    public $maxbytes;
56
 
57
    /** @var int The number of attachments required for a response to be complete. */
58
    public $attachmentsrequired;
59
 
60
    public $graderinfo;
61
    public $graderinfoformat;
62
    public $responsetemplate;
63
    public $responsetemplateformat;
64
 
65
    /** @var array The string array of file types accepted upon file submission. */
66
    public $filetypeslist;
67
 
68
    public function make_behaviour(question_attempt $qa, $preferredbehaviour) {
69
        return question_engine::make_behaviour('manualgraded', $qa, $preferredbehaviour);
70
    }
71
 
72
    /**
73
     * @param moodle_page the page we are outputting to.
74
     * @return qtype_essay_format_renderer_base the response-format-specific renderer.
75
     */
76
    public function get_format_renderer(moodle_page $page) {
77
        return $page->get_renderer('qtype_essay', 'format_' . $this->responseformat);
78
    }
79
 
80
    public function get_expected_data() {
81
        if ($this->responseformat == 'editorfilepicker') {
82
            $expecteddata = array('answer' => question_attempt::PARAM_RAW_FILES);
83
        } else {
84
            $expecteddata = array('answer' => PARAM_RAW);
85
        }
86
        $expecteddata['answerformat'] = PARAM_ALPHANUMEXT;
87
        if ($this->attachments != 0) {
88
            $expecteddata['attachments'] = question_attempt::PARAM_FILES;
89
        }
90
        return $expecteddata;
91
    }
92
 
93
    public function summarise_response(array $response) {
94
        $output = null;
95
 
96
        if (isset($response['answer'])) {
97
            $output .= question_utils::to_plain_text($response['answer'],
98
                $response['answerformat'], array('para' => false));
99
        }
100
 
101
        if (isset($response['attachments'])  && $response['attachments']) {
102
            $attachedfiles = [];
103
            foreach ($response['attachments']->get_files() as $file) {
104
                $attachedfiles[] = $file->get_filename() . ' (' . display_size($file->get_filesize()) . ')';
105
            }
106
            if ($attachedfiles) {
107
                $output .= get_string('attachedfiles', 'qtype_essay', implode(', ', $attachedfiles));
108
            }
109
        }
110
        return $output;
111
    }
112
 
113
    public function un_summarise_response(string $summary) {
114
        if (empty($summary)) {
115
            return [];
116
        }
117
 
118
        if (str_contains($this->responseformat, 'editor')) {
119
            return ['answer' => text_to_html($summary), 'answerformat' => FORMAT_HTML];
120
        } else {
121
            return ['answer' => $summary, 'answerformat' => FORMAT_PLAIN];
122
        }
123
    }
124
 
125
    public function get_correct_response() {
126
        return null;
127
    }
128
 
129
    public function is_complete_response(array $response) {
130
        // Determine if the given response has online text and attachments.
131
        $hasinlinetext = array_key_exists('answer', $response) && ($response['answer'] !== '');
132
 
133
        // If there is a response and min/max word limit is set in the form then validate the number of words in response.
134
        if ($hasinlinetext) {
135
            if ($this->check_input_word_count($response['answer'])) {
136
                return false;
137
            }
138
        }
139
        $hasattachments = array_key_exists('attachments', $response)
140
            && $response['attachments'] instanceof question_response_files;
141
 
142
        // Determine the number of attachments present.
143
        if ($hasattachments) {
144
            // Check the filetypes.
145
            $filetypesutil = new \core_form\filetypes_util();
146
            $allowlist = $filetypesutil->normalize_file_types($this->filetypeslist);
147
            $wrongfiles = array();
148
            foreach ($response['attachments']->get_files() as $file) {
149
                if (!$filetypesutil->is_allowed_file_type($file->get_filename(), $allowlist)) {
150
                    $wrongfiles[] = $file->get_filename();
151
                }
152
            }
153
            if ($wrongfiles) { // At least one filetype is wrong.
154
                return false;
155
            }
156
            $attachcount = count($response['attachments']->get_files());
157
        } else {
158
            $attachcount = 0;
159
        }
160
 
161
        // Determine if we have /some/ content to be graded.
162
        $hascontent = $hasinlinetext || ($attachcount > 0);
163
 
164
        // Determine if we meet the optional requirements.
165
        $meetsinlinereq = $hasinlinetext || (!$this->responserequired) || ($this->responseformat == 'noinline');
166
        $meetsattachmentreq = ($attachcount >= $this->attachmentsrequired);
167
 
168
        // The response is complete iff all of our requirements are met.
169
        return $hascontent && $meetsinlinereq && $meetsattachmentreq;
170
    }
171
 
172
    /**
173
     * Return null if is_complete_response() returns true
174
     * otherwise, return the minmax-limit error message
175
     *
176
     * @param array $response
177
     * @return string
178
     */
179
    public function get_validation_error(array $response) {
180
        if ($this->is_complete_response($response)) {
181
            return '';
182
        }
183
        return $this->check_input_word_count($response['answer']);
184
    }
185
 
186
    public function is_gradable_response(array $response) {
187
        // Determine if the given response has online text and attachments.
188
        if (array_key_exists('answer', $response) && ($response['answer'] !== '')) {
189
            return true;
190
        } else if (array_key_exists('attachments', $response)
191
                && $response['attachments'] instanceof question_response_files) {
192
            return true;
193
        } else {
194
            return false;
195
        }
196
    }
197
 
198
    public function is_same_response(array $prevresponse, array $newresponse) {
199
        if (array_key_exists('answer', $prevresponse) && $prevresponse['answer'] !== $this->responsetemplate) {
200
            $value1 = (string) $prevresponse['answer'];
201
        } else {
202
            $value1 = '';
203
        }
204
        if (array_key_exists('answer', $newresponse) && $newresponse['answer'] !== $this->responsetemplate) {
205
            $value2 = (string) $newresponse['answer'];
206
        } else {
207
            $value2 = '';
208
        }
209
        return $value1 === $value2 && ($this->attachments == 0 ||
210
                question_utils::arrays_same_at_key_missing_is_blank(
211
                $prevresponse, $newresponse, 'attachments'));
212
    }
213
 
214
    public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) {
215
        if ($component == 'question' && $filearea == 'response_attachments') {
216
            // Response attachments visible if the question has them.
217
            return $this->attachments != 0;
218
 
219
        } else if ($component == 'question' && $filearea == 'response_answer') {
220
            // Response attachments visible if the question has them.
221
            return $this->responseformat === 'editorfilepicker';
222
 
223
        } else if ($component == 'qtype_essay' && $filearea == 'graderinfo') {
224
            return $options->manualcomment && $args[0] == $this->id;
225
 
226
        } else {
227
            return parent::check_file_access($qa, $options, $component,
228
                    $filearea, $args, $forcedownload);
229
        }
230
    }
231
 
232
    /**
233
     * Return the question settings that define this question as structured data.
234
     *
235
     * @param question_attempt $qa the current attempt for which we are exporting the settings.
236
     * @param question_display_options $options the question display options which say which aspects of the question
237
     * should be visible.
238
     * @return mixed structure representing the question settings. In web services, this will be JSON-encoded.
239
     */
240
    public function get_question_definition_for_external_rendering(question_attempt $qa, question_display_options $options) {
241
        // This is a partial implementation, returning only the most relevant question settings for now,
242
        // ideally, we should return as much as settings as possible (depending on the state and display options).
243
 
244
        $settings = [
245
            'responseformat' => $this->responseformat,
246
            'responserequired' => $this->responserequired,
247
            'responsefieldlines' => $this->responsefieldlines,
248
            'attachments' => $this->attachments,
249
            'attachmentsrequired' => $this->attachmentsrequired,
250
            'maxbytes' => $this->maxbytes,
251
            'filetypeslist' => $this->filetypeslist,
252
            'responsetemplate' => $this->responsetemplate,
253
            'responsetemplateformat' => $this->responsetemplateformat,
254
            'minwordlimit' => $this->minwordlimit,
255
            'maxwordlimit' => $this->maxwordlimit,
256
        ];
257
 
258
        return $settings;
259
    }
260
 
261
    /**
262
     * Check the input word count and return a message to user
263
     * when the number of words are outside the boundary settings.
264
     *
265
     * @param string $responsestring
266
     * @return string|null
267
     .*/
268
    private function check_input_word_count($responsestring) {
269
        if (!$this->responserequired) {
270
            return null;
271
        }
272
        if (!$this->minwordlimit && !$this->maxwordlimit) {
273
            // This question does not care about the word count.
274
            return null;
275
        }
276
 
277
        // Count the number of words in the response string.
278
        $count = count_words($responsestring);
279
        if ($this->maxwordlimit && $count > $this->maxwordlimit) {
280
            return get_string('maxwordlimitboundary', 'qtype_essay',
281
                    ['limit' => $this->maxwordlimit, 'count' => $count]);
282
        } else if ($count < $this->minwordlimit) {
283
            return get_string('minwordlimitboundary', 'qtype_essay',
284
                    ['limit' => $this->minwordlimit, 'count' => $count]);
285
        } else {
286
            return null;
287
        }
288
    }
289
 
290
    /**
291
     * If this question uses word counts, then return a display of the current
292
     * count, and whether it is within limit, for when the question is being reviewed.
293
     *
294
     * @param array $response responses, as returned by
295
     *      {@see question_attempt_step::get_qt_data()}.
296
     * @return string If relevant to this question, a display of the word count.
297
     */
298
    public function get_word_count_message_for_review(array $response): string {
299
        if (!$this->minwordlimit && !$this->maxwordlimit) {
300
            // This question does not care about the word count.
301
            return '';
302
        }
303
 
304
        if (!array_key_exists('answer', $response) || ($response['answer'] === '')) {
305
            // No response.
306
            return '';
307
        }
308
 
309
        $count = count_words($response['answer']);
310
        if ($this->maxwordlimit && $count > $this->maxwordlimit) {
311
            return get_string('wordcounttoomuch', 'qtype_essay',
312
                    ['limit' => $this->maxwordlimit, 'count' => $count]);
313
        } else if ($count < $this->minwordlimit) {
314
            return get_string('wordcounttoofew', 'qtype_essay',
315
                    ['limit' => $this->minwordlimit, 'count' => $count]);
316
        } else {
317
            return get_string('wordcount', 'qtype_essay', $count);
318
        }
319
    }
320
}