Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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 renderer 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
 
30
/**
31
 * Generates the output for essay questions.
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_essay_renderer extends qtype_renderer {
37
    public function formulation_and_controls(question_attempt $qa,
38
            question_display_options $options) {
39
        global $CFG;
40
        $question = $qa->get_question();
41
 
42
        /** @var qtype_essay_format_renderer_base $responseoutput */
43
        $responseoutput = $question->get_format_renderer($this->page);
44
        $responseoutput->set_displayoptions($options);
45
 
46
        // Answer field.
47
        $step = $qa->get_last_step_with_qt_var('answer');
48
 
49
        if (!$step->has_qt_var('answer') && empty($options->readonly)) {
50
            // Question has never been answered, fill it with response template.
51
            $step = new question_attempt_step(array('answer' => $question->responsetemplate));
52
        }
53
 
54
        if (empty($options->readonly)) {
55
            $answer = $responseoutput->response_area_input('answer', $qa,
56
                    $step, $question->responsefieldlines, $options->context);
57
 
58
        } else {
59
            $answer = $responseoutput->response_area_read_only('answer', $qa,
60
                    $step, $question->responsefieldlines, $options->context);
61
            $answer .= html_writer::nonempty_tag('p', $question->get_word_count_message_for_review($step->get_qt_data()));
62
 
63
            if (!empty($CFG->enableplagiarism)) {
64
                require_once($CFG->libdir . '/plagiarismlib.php');
65
 
66
                $answer .= plagiarism_get_links([
67
                    'context' => $options->context->id,
68
                    'component' => $qa->get_question()->qtype->plugin_name(),
69
                    'area' => $qa->get_usage_id(),
70
                    'itemid' => $qa->get_slot(),
71
                    'userid' => $step->get_user_id(),
72
                    'content' => $qa->get_response_summary()]);
73
            }
74
        }
75
 
76
        $files = '';
77
        if ($question->attachments) {
78
            if (empty($options->readonly)) {
79
                $files = $this->files_input($qa, $question->attachments, $options);
80
 
81
            } else {
82
                $files = $this->files_read_only($qa, $options);
83
            }
84
        }
85
 
86
        $result = '';
87
        $result .= html_writer::tag('div', $question->format_questiontext($qa),
88
                array('class' => 'qtext'));
89
 
90
        $result .= html_writer::start_tag('div', array('class' => 'ablock'));
91
        $result .= html_writer::tag('div', $answer, array('class' => 'answer'));
92
 
93
        // If there is a response and min/max word limit is set in the form then check the response word count.
94
        if ($qa->get_state() == question_state::$invalid) {
95
            $result .= html_writer::nonempty_tag('div',
96
                $question->get_validation_error($step->get_qt_data()), ['class' => 'validationerror']);
97
        }
98
        $result .= html_writer::tag('div', $files, array('class' => 'attachments'));
99
        $result .= html_writer::end_tag('div');
100
 
101
        return $result;
102
    }
103
 
104
    /**
105
     * Displays any attached files when the question is in read-only mode.
106
     * @param question_attempt $qa the question attempt to display.
107
     * @param question_display_options $options controls what should and should
108
     *      not be displayed. Used to get the context.
109
     */
110
    public function files_read_only(question_attempt $qa, question_display_options $options) {
111
        global $CFG;
112
        $files = $qa->get_last_qt_files('attachments', $options->context->id);
113
        $filelist = [];
114
 
115
        $step = $qa->get_last_step_with_qt_var('attachments');
116
 
117
        foreach ($files as $file) {
118
            $out = html_writer::link($qa->get_response_file_url($file),
119
                $this->output->pix_icon(file_file_icon($file), get_mimetype_description($file),
120
                    'moodle', array('class' => 'icon')) . ' ' . s($file->get_filename()));
121
            if (!empty($CFG->enableplagiarism)) {
122
                require_once($CFG->libdir . '/plagiarismlib.php');
123
 
124
                $out .= plagiarism_get_links([
125
                    'context' => $options->context->id,
126
                    'component' => $qa->get_question()->qtype->plugin_name(),
127
                    'area' => $qa->get_usage_id(),
128
                    'itemid' => $qa->get_slot(),
129
                    'userid' => $step->get_user_id(),
130
                    'file' => $file]);
131
            }
132
            $filelist[] = html_writer::tag('li', $out, ['class' => 'mb-2']);
133
        }
134
 
135
        $labelbyid = $qa->get_qt_field_name('attachments') . '_label';
136
 
137
        $fileslabel = $options->add_question_identifier_to_label(get_string('answerfiles', 'qtype_essay'));
1441 ariadna 138
        $output = html_writer::tag('h4', $fileslabel, ['id' => $labelbyid, 'class' => 'visually-hidden']);
1 efrain 139
        $output .= html_writer::tag('ul', implode($filelist), [
140
            'aria-labelledby' => $labelbyid,
141
            'class' => 'list-unstyled m-0',
142
        ]);
143
        return $output;
144
    }
145
 
146
    /**
147
     * Displays the input control for when the student should upload a single file.
148
     * @param question_attempt $qa the question attempt to display.
149
     * @param int $numallowed the maximum number of attachments allowed. -1 = unlimited.
150
     * @param question_display_options $options controls what should and should
151
     *      not be displayed. Used to get the context.
152
     */
153
    public function files_input(question_attempt $qa, $numallowed,
154
            question_display_options $options) {
155
        global $CFG, $COURSE;
156
        require_once($CFG->dirroot . '/lib/form/filemanager.php');
157
 
158
        $pickeroptions = new stdClass();
159
        $pickeroptions->mainfile = null;
160
        $pickeroptions->maxfiles = $numallowed;
161
        $pickeroptions->itemid = $qa->prepare_response_files_draft_itemid(
162
                'attachments', $options->context->id);
163
        $pickeroptions->context = $options->context;
164
        $pickeroptions->return_types = FILE_INTERNAL | FILE_CONTROLLED_LINK;
165
        $pickeroptions->accepted_types = $qa->get_question()->filetypeslist;
166
 
167
        $fm = new form_filemanager($pickeroptions);
168
        $fm->options->maxbytes = get_user_max_upload_file_size(
169
            $this->page->context,
170
            $CFG->maxbytes,
171
            $COURSE->maxbytes,
172
            $qa->get_question()->maxbytes
173
        );
174
        $filesrenderer = $this->page->get_renderer('core', 'files');
175
 
176
        $text = '';
177
        if (!empty($qa->get_question()->filetypeslist)) {
178
            $text = html_writer::tag('p', get_string('acceptedfiletypes', 'qtype_essay'));
179
            $filetypesutil = new \core_form\filetypes_util();
180
            $filetypes = $qa->get_question()->filetypeslist;
181
            $filetypedescriptions = $filetypesutil->describe_file_types($filetypes);
182
            $text .= $this->render_from_template('core_form/filetypes-descriptions', $filetypedescriptions);
183
        }
184
 
185
        $output = html_writer::start_tag('fieldset');
186
        $fileslabel = $options->add_question_identifier_to_label(get_string('answerfiles', 'qtype_essay'));
1441 ariadna 187
        $output .= html_writer::tag('legend', $fileslabel, ['class' => 'visually-hidden']);
1 efrain 188
        $output .= $filesrenderer->render($fm);
189
        $output .= html_writer::empty_tag('input', [
190
            'type' => 'hidden',
191
            'name' => $qa->get_qt_field_name('attachments'),
192
            'value' => $pickeroptions->itemid,
193
        ]);
194
        $output .= $text;
195
        $output .= html_writer::end_tag('fieldset');
196
 
197
        return $output;
198
    }
199
 
200
    public function manual_comment(question_attempt $qa, question_display_options $options) {
201
        if ($options->manualcomment != question_display_options::EDITABLE) {
202
            return '';
203
        }
204
 
205
        $question = $qa->get_question();
206
        return html_writer::nonempty_tag('div', $question->format_text(
207
                $question->graderinfo, $question->graderinfoformat, $qa, 'qtype_essay',
208
                'graderinfo', $question->id), array('class' => 'graderinfo'));
209
    }
210
}
211
 
212
 
213
/**
214
 * A base class to abstract out the differences between different type of
215
 * response format.
216
 *
217
 * @copyright  2011 The Open University
218
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
219
 */
220
abstract class qtype_essay_format_renderer_base extends plugin_renderer_base {
221
 
222
    /** @var question_display_options Question display options instance for any necessary information for rendering the question. */
223
    protected $displayoptions;
224
 
225
    /**
226
     * Question number setter.
227
     *
228
     * @param question_display_options $displayoptions
229
     */
230
    public function set_displayoptions(question_display_options $displayoptions): void {
231
        $this->displayoptions = $displayoptions;
232
    }
233
 
234
    /**
235
     * Render the students respone when the question is in read-only mode.
236
     * @param string $name the variable name this input edits.
237
     * @param question_attempt $qa the question attempt being display.
238
     * @param question_attempt_step $step the current step.
239
     * @param int $lines approximate size of input box to display.
240
     * @param object $context the context teh output belongs to.
241
     * @return string html to display the response.
242
     */
243
    abstract public function response_area_read_only($name, question_attempt $qa,
244
            question_attempt_step $step, $lines, $context);
245
 
246
    /**
247
     * Render the students respone when the question is in read-only mode.
248
     * @param string $name the variable name this input edits.
249
     * @param question_attempt $qa the question attempt being display.
250
     * @param question_attempt_step $step the current step.
251
     * @param int $lines approximate size of input box to display.
252
     * @param object $context the context teh output belongs to.
253
     * @return string html to display the response for editing.
254
     */
255
    abstract public function response_area_input($name, question_attempt $qa,
256
            question_attempt_step $step, $lines, $context);
257
 
258
    /**
259
     * @return string specific class name to add to the input element.
260
     */
261
    abstract protected function class_name();
262
}
263
 
264
/**
265
 * An essay format renderer for essays where the student should not enter
266
 * any inline response.
267
 *
268
 * @copyright  2013 Binghamton University
269
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
270
 */
271
class qtype_essay_format_noinline_renderer extends qtype_essay_format_renderer_base {
272
 
273
    protected function class_name() {
274
        return 'qtype_essay_noinline';
275
    }
276
 
277
    public function response_area_read_only($name, $qa, $step, $lines, $context) {
278
        return '';
279
    }
280
 
281
    public function response_area_input($name, $qa, $step, $lines, $context) {
282
        return '';
283
    }
284
 
285
}
286
 
287
/**
288
 * An essay format renderer for essays where the student should use the HTML
289
 * editor without the file picker.
290
 *
291
 * @copyright  2011 The Open University
292
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
293
 */
294
class qtype_essay_format_editor_renderer extends qtype_essay_format_renderer_base {
295
    protected function class_name() {
296
        return 'qtype_essay_editor';
297
    }
298
 
299
    public function response_area_read_only($name, $qa, $step, $lines, $context) {
300
        $labelbyid = $qa->get_qt_field_name($name) . '_label';
301
 
302
        $responselabel = $this->displayoptions->add_question_identifier_to_label(get_string('answertext', 'qtype_essay'));
1441 ariadna 303
        $output = html_writer::tag('h4', $responselabel, ['id' => $labelbyid, 'class' => 'visually-hidden']);
1 efrain 304
        $output .= html_writer::tag('div', $this->prepare_response($name, $qa, $step, $context), [
305
            'role' => 'textbox',
306
            'aria-readonly' => 'true',
307
            'aria-labelledby' => $labelbyid,
308
            'class' => $this->class_name() . ' qtype_essay_response readonly',
309
            'style' => 'min-height: ' . ($lines * 1.5) . 'em;',
310
        ]);
311
        // Height $lines * 1.5 because that is a typical line-height on web pages.
312
        // That seems to give results that look OK.
313
 
314
        return $output;
315
    }
316
 
317
    public function response_area_input($name, $qa, $step, $lines, $context) {
318
        global $CFG;
319
        require_once($CFG->dirroot . '/repository/lib.php');
320
 
321
        $inputname = $qa->get_qt_field_name($name);
322
        $responseformat = $step->get_qt_var($name . 'format');
323
        $id = $inputname . '_id';
324
 
325
        $editor = editors_get_preferred_editor($responseformat);
326
        $strformats = format_text_menu();
327
        $formats = $editor->get_supported_formats();
328
        foreach ($formats as $fid) {
329
            $formats[$fid] = $strformats[$fid];
330
        }
331
 
332
        list($draftitemid, $response) = $this->prepare_response_for_editing(
333
                $name, $step, $context);
334
 
335
        $editor->set_text($response);
336
        $editor->use_editor($id, $this->get_editor_options($context),
337
                $this->get_filepicker_options($context, $draftitemid));
338
 
339
        $responselabel = $this->displayoptions->add_question_identifier_to_label(get_string('answertext', 'qtype_essay'));
340
        $output = html_writer::tag('label', $responselabel, [
1441 ariadna 341
            'class' => 'visually-hidden',
1 efrain 342
            'for' => $id,
343
        ]);
344
        $output .= html_writer::start_tag('div', array('class' =>
345
                $this->class_name() . ' qtype_essay_response'));
346
 
347
        $output .= html_writer::tag('div', html_writer::tag('textarea', s($response),
348
                array('id' => $id, 'name' => $inputname, 'rows' => $lines, 'cols' => 60, 'class' => 'form-control')));
349
 
350
        $output .= html_writer::start_tag('div');
351
        if (count($formats) == 1) {
352
            reset($formats);
353
            $output .= html_writer::empty_tag('input', array('type' => 'hidden',
354
                    'name' => $inputname . 'format', 'value' => key($formats)));
355
 
356
        } else {
357
            $output .= html_writer::label(get_string('format'), 'menu' . $inputname . 'format', false);
358
            $output .= ' ';
359
            $output .= html_writer::select($formats, $inputname . 'format', $responseformat, '');
360
        }
361
        $output .= html_writer::end_tag('div');
362
 
363
        $output .= $this->filepicker_html($inputname, $draftitemid);
364
 
365
        $output .= html_writer::end_tag('div');
366
        return $output;
367
    }
368
 
369
    /**
370
     * Prepare the response for read-only display.
371
     * @param string $name the variable name this input edits.
372
     * @param question_attempt $qa the question attempt being display.
373
     * @param question_attempt_step $step the current step.
374
     * @param object $context the context the attempt belongs to.
375
     * @return string the response prepared for display.
376
     */
377
    protected function prepare_response($name, question_attempt $qa,
378
            question_attempt_step $step, $context) {
379
        if (!$step->has_qt_var($name)) {
380
            return '';
381
        }
382
 
383
        $formatoptions = new stdClass();
384
        $formatoptions->para = false;
385
        return format_text($step->get_qt_var($name), $step->get_qt_var($name . 'format'),
386
                $formatoptions);
387
    }
388
 
389
    /**
390
     * Prepare the response for editing.
391
     * @param string $name the variable name this input edits.
392
     * @param question_attempt_step $step the current step.
393
     * @param object $context the context the attempt belongs to.
394
     * @return string the response prepared for display.
395
     */
396
    protected function prepare_response_for_editing($name,
397
            question_attempt_step $step, $context) {
398
        return array(0, $step->get_qt_var($name));
399
    }
400
 
401
    /**
402
     * @param object $context the context the attempt belongs to.
403
     * @return array options for the editor.
404
     */
405
    protected function get_editor_options($context) {
406
        // Disable the text-editor autosave because quiz has it's own auto save function.
407
        return array('context' => $context, 'autosave' => false);
408
    }
409
 
410
    /**
411
     * @param object $context the context the attempt belongs to.
412
     * @param int $draftitemid draft item id.
413
     * @return array filepicker options for the editor.
414
     */
415
    protected function get_filepicker_options($context, $draftitemid) {
416
        return array('return_types'  => FILE_INTERNAL | FILE_EXTERNAL);
417
    }
418
 
419
    /**
420
     * @param string $inputname input field name.
421
     * @param int $draftitemid draft file area itemid.
422
     * @return string HTML for the filepicker, if used.
423
     */
424
    protected function filepicker_html($inputname, $draftitemid) {
425
        return '';
426
    }
427
}
428
 
429
 
430
/**
431
 * An essay format renderer for essays where the student should use the HTML
432
 * editor with the file picker.
433
 *
434
 * @copyright  2011 The Open University
435
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
436
 */
437
class qtype_essay_format_editorfilepicker_renderer extends qtype_essay_format_editor_renderer {
438
    protected function class_name() {
439
        return 'qtype_essay_editorfilepicker';
440
    }
441
 
442
    protected function prepare_response($name, question_attempt $qa,
443
            question_attempt_step $step, $context) {
444
        if (!$step->has_qt_var($name)) {
445
            return '';
446
        }
447
 
448
        $formatoptions = new stdClass();
449
        $formatoptions->para = false;
450
        $text = $qa->rewrite_response_pluginfile_urls($step->get_qt_var($name),
451
                $context->id, 'answer', $step);
452
        return format_text($text, $step->get_qt_var($name . 'format'), $formatoptions);
453
    }
454
 
455
    protected function prepare_response_for_editing($name,
456
            question_attempt_step $step, $context) {
457
        return $step->prepare_response_files_draft_itemid_with_text(
458
                $name, $context->id, $step->get_qt_var($name));
459
    }
460
 
461
    /**
462
     * Get editor options for question response text area.
463
     * @param object $context the context the attempt belongs to.
464
     * @return array options for the editor.
465
     */
466
    protected function get_editor_options($context) {
467
        return question_utils::get_editor_options($context);
468
    }
469
 
470
    /**
471
     * Get the options required to configure the filepicker for one of the editor
472
     * toolbar buttons.
473
     * @deprecated since 3.5
474
     * @param mixed $acceptedtypes array of types of '*'.
475
     * @param int $draftitemid the draft area item id.
476
     * @param object $context the context.
477
     * @return object the required options.
478
     */
479
    protected function specific_filepicker_options($acceptedtypes, $draftitemid, $context) {
480
        debugging('qtype_essay_format_editorfilepicker_renderer::specific_filepicker_options() is deprecated, ' .
481
            'use question_utils::specific_filepicker_options() instead.', DEBUG_DEVELOPER);
482
 
483
        $filepickeroptions = new stdClass();
484
        $filepickeroptions->accepted_types = $acceptedtypes;
485
        $filepickeroptions->return_types = FILE_INTERNAL | FILE_EXTERNAL;
486
        $filepickeroptions->context = $context;
487
        $filepickeroptions->env = 'filepicker';
488
 
489
        $options = initialise_filepicker($filepickeroptions);
490
        $options->context = $context;
491
        $options->client_id = uniqid();
492
        $options->env = 'editor';
493
        $options->itemid = $draftitemid;
494
 
495
        return $options;
496
    }
497
 
498
    /**
499
     * @param object $context the context the attempt belongs to.
500
     * @param int $draftitemid draft item id.
501
     * @return array filepicker options for the editor.
502
     */
503
    protected function get_filepicker_options($context, $draftitemid) {
504
        return question_utils::get_filepicker_options($context, $draftitemid);
505
    }
506
 
507
    protected function filepicker_html($inputname, $draftitemid) {
508
        $nonjspickerurl = new moodle_url('/repository/draftfiles_manager.php', array(
509
            'action' => 'browse',
510
            'env' => 'editor',
511
            'itemid' => $draftitemid,
512
            'subdirs' => false,
513
            'maxfiles' => -1,
514
            'sesskey' => sesskey(),
515
        ));
516
 
517
        return html_writer::empty_tag('input', array('type' => 'hidden',
518
                'name' => $inputname . ':itemid', 'value' => $draftitemid)) .
519
                html_writer::tag('noscript', html_writer::tag('div',
520
                    html_writer::tag('object', '', array('type' => 'text/html',
521
                        'data' => $nonjspickerurl, 'height' => 160, 'width' => 600,
522
                        'style' => 'border: 1px solid #000;'))));
523
    }
524
}
525
 
526
 
527
/**
528
 * An essay format renderer for essays where the student should use a plain
529
 * input box, but with a normal, proportional font.
530
 *
531
 * @copyright  2011 The Open University
532
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
533
 */
534
class qtype_essay_format_plain_renderer extends qtype_essay_format_renderer_base {
535
    /**
536
     * @return string the HTML for the textarea.
537
     */
538
    protected function textarea($response, $lines, $attributes) {
539
        $attributes['class'] = $this->class_name() . ' qtype_essay_response form-control';
540
        $attributes['rows'] = $lines;
541
        $attributes['cols'] = 60;
542
        return html_writer::tag('textarea', s($response), $attributes);
543
    }
544
 
545
    protected function class_name() {
546
        return 'qtype_essay_plain';
547
    }
548
 
549
    public function response_area_read_only($name, $qa, $step, $lines, $context) {
550
        $id = $qa->get_qt_field_name($name) . '_id';
551
 
552
        $responselabel = $this->displayoptions->add_question_identifier_to_label(get_string('answertext', 'qtype_essay'));
1441 ariadna 553
        $output = html_writer::tag('label', $responselabel, ['class' => 'visually-hidden', 'for' => $id]);
1 efrain 554
        $output .= $this->textarea($step->get_qt_var($name), $lines, ['id' => $id, 'readonly' => 'readonly']);
555
        return $output;
556
    }
557
 
558
    public function response_area_input($name, $qa, $step, $lines, $context) {
559
        $inputname = $qa->get_qt_field_name($name);
560
        $id = $inputname . '_id';
561
 
562
        $responselabel = $this->displayoptions->add_question_identifier_to_label(get_string('answertext', 'qtype_essay'));
1441 ariadna 563
        $output = html_writer::tag('label', $responselabel, ['class' => 'visually-hidden', 'for' => $id]);
1 efrain 564
        $output .= $this->textarea($step->get_qt_var($name), $lines, ['name' => $inputname, 'id' => $id]);
565
        $output .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => $inputname . 'format', 'value' => FORMAT_PLAIN]);
566
 
567
        return $output;
568
    }
569
}
570
 
571
 
572
/**
573
 * An essay format renderer for essays where the student should use a plain
574
 * input box with a monospaced font. You might use this, for example, for a
575
 * question where the students should type computer code.
576
 *
577
 * @copyright  2011 The Open University
578
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
579
 */
580
class qtype_essay_format_monospaced_renderer extends qtype_essay_format_plain_renderer {
581
    protected function class_name() {
582
        return 'qtype_essay_monospaced';
583
    }
584
}