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 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'));
138
        $output = html_writer::tag('h4', $fileslabel, ['id' => $labelbyid, 'class' => 'sr-only']);
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
 
166
        $pickeroptions->itemid = $qa->prepare_response_files_draft_itemid(
167
                'attachments', $options->context->id);
168
        $pickeroptions->accepted_types = $qa->get_question()->filetypeslist;
169
 
170
        $fm = new form_filemanager($pickeroptions);
171
        $fm->options->maxbytes = get_user_max_upload_file_size(
172
            $this->page->context,
173
            $CFG->maxbytes,
174
            $COURSE->maxbytes,
175
            $qa->get_question()->maxbytes
176
        );
177
        $filesrenderer = $this->page->get_renderer('core', 'files');
178
 
179
        $text = '';
180
        if (!empty($qa->get_question()->filetypeslist)) {
181
            $text = html_writer::tag('p', get_string('acceptedfiletypes', 'qtype_essay'));
182
            $filetypesutil = new \core_form\filetypes_util();
183
            $filetypes = $qa->get_question()->filetypeslist;
184
            $filetypedescriptions = $filetypesutil->describe_file_types($filetypes);
185
            $text .= $this->render_from_template('core_form/filetypes-descriptions', $filetypedescriptions);
186
        }
187
 
188
        $output = html_writer::start_tag('fieldset');
189
        $fileslabel = $options->add_question_identifier_to_label(get_string('answerfiles', 'qtype_essay'));
190
        $output .= html_writer::tag('legend', $fileslabel, ['class' => 'sr-only']);
191
        $output .= $filesrenderer->render($fm);
192
        $output .= html_writer::empty_tag('input', [
193
            'type' => 'hidden',
194
            'name' => $qa->get_qt_field_name('attachments'),
195
            'value' => $pickeroptions->itemid,
196
        ]);
197
        $output .= $text;
198
        $output .= html_writer::end_tag('fieldset');
199
 
200
        return $output;
201
    }
202
 
203
    public function manual_comment(question_attempt $qa, question_display_options $options) {
204
        if ($options->manualcomment != question_display_options::EDITABLE) {
205
            return '';
206
        }
207
 
208
        $question = $qa->get_question();
209
        return html_writer::nonempty_tag('div', $question->format_text(
210
                $question->graderinfo, $question->graderinfoformat, $qa, 'qtype_essay',
211
                'graderinfo', $question->id), array('class' => 'graderinfo'));
212
    }
213
}
214
 
215
 
216
/**
217
 * A base class to abstract out the differences between different type of
218
 * response format.
219
 *
220
 * @copyright  2011 The Open University
221
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
222
 */
223
abstract class qtype_essay_format_renderer_base extends plugin_renderer_base {
224
 
225
    /** @var question_display_options Question display options instance for any necessary information for rendering the question. */
226
    protected $displayoptions;
227
 
228
    /**
229
     * Question number setter.
230
     *
231
     * @param question_display_options $displayoptions
232
     */
233
    public function set_displayoptions(question_display_options $displayoptions): void {
234
        $this->displayoptions = $displayoptions;
235
    }
236
 
237
    /**
238
     * Render the students respone when the question is in read-only mode.
239
     * @param string $name the variable name this input edits.
240
     * @param question_attempt $qa the question attempt being display.
241
     * @param question_attempt_step $step the current step.
242
     * @param int $lines approximate size of input box to display.
243
     * @param object $context the context teh output belongs to.
244
     * @return string html to display the response.
245
     */
246
    abstract public function response_area_read_only($name, question_attempt $qa,
247
            question_attempt_step $step, $lines, $context);
248
 
249
    /**
250
     * Render the students respone when the question is in read-only mode.
251
     * @param string $name the variable name this input edits.
252
     * @param question_attempt $qa the question attempt being display.
253
     * @param question_attempt_step $step the current step.
254
     * @param int $lines approximate size of input box to display.
255
     * @param object $context the context teh output belongs to.
256
     * @return string html to display the response for editing.
257
     */
258
    abstract public function response_area_input($name, question_attempt $qa,
259
            question_attempt_step $step, $lines, $context);
260
 
261
    /**
262
     * @return string specific class name to add to the input element.
263
     */
264
    abstract protected function class_name();
265
}
266
 
267
/**
268
 * An essay format renderer for essays where the student should not enter
269
 * any inline response.
270
 *
271
 * @copyright  2013 Binghamton University
272
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
273
 */
274
class qtype_essay_format_noinline_renderer extends qtype_essay_format_renderer_base {
275
 
276
    protected function class_name() {
277
        return 'qtype_essay_noinline';
278
    }
279
 
280
    public function response_area_read_only($name, $qa, $step, $lines, $context) {
281
        return '';
282
    }
283
 
284
    public function response_area_input($name, $qa, $step, $lines, $context) {
285
        return '';
286
    }
287
 
288
}
289
 
290
/**
291
 * An essay format renderer for essays where the student should use the HTML
292
 * editor without the file picker.
293
 *
294
 * @copyright  2011 The Open University
295
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
296
 */
297
class qtype_essay_format_editor_renderer extends qtype_essay_format_renderer_base {
298
    protected function class_name() {
299
        return 'qtype_essay_editor';
300
    }
301
 
302
    public function response_area_read_only($name, $qa, $step, $lines, $context) {
303
        $labelbyid = $qa->get_qt_field_name($name) . '_label';
304
 
305
        $responselabel = $this->displayoptions->add_question_identifier_to_label(get_string('answertext', 'qtype_essay'));
306
        $output = html_writer::tag('h4', $responselabel, ['id' => $labelbyid, 'class' => 'sr-only']);
307
        $output .= html_writer::tag('div', $this->prepare_response($name, $qa, $step, $context), [
308
            'role' => 'textbox',
309
            'aria-readonly' => 'true',
310
            'aria-labelledby' => $labelbyid,
311
            'class' => $this->class_name() . ' qtype_essay_response readonly',
312
            'style' => 'min-height: ' . ($lines * 1.5) . 'em;',
313
        ]);
314
        // Height $lines * 1.5 because that is a typical line-height on web pages.
315
        // That seems to give results that look OK.
316
 
317
        return $output;
318
    }
319
 
320
    public function response_area_input($name, $qa, $step, $lines, $context) {
321
        global $CFG;
322
        require_once($CFG->dirroot . '/repository/lib.php');
323
 
324
        $inputname = $qa->get_qt_field_name($name);
325
        $responseformat = $step->get_qt_var($name . 'format');
326
        $id = $inputname . '_id';
327
 
328
        $editor = editors_get_preferred_editor($responseformat);
329
        $strformats = format_text_menu();
330
        $formats = $editor->get_supported_formats();
331
        foreach ($formats as $fid) {
332
            $formats[$fid] = $strformats[$fid];
333
        }
334
 
335
        list($draftitemid, $response) = $this->prepare_response_for_editing(
336
                $name, $step, $context);
337
 
338
        $editor->set_text($response);
339
        $editor->use_editor($id, $this->get_editor_options($context),
340
                $this->get_filepicker_options($context, $draftitemid));
341
 
342
        $responselabel = $this->displayoptions->add_question_identifier_to_label(get_string('answertext', 'qtype_essay'));
343
        $output = html_writer::tag('label', $responselabel, [
344
            'class' => 'sr-only',
345
            'for' => $id,
346
        ]);
347
        $output .= html_writer::start_tag('div', array('class' =>
348
                $this->class_name() . ' qtype_essay_response'));
349
 
350
        $output .= html_writer::tag('div', html_writer::tag('textarea', s($response),
351
                array('id' => $id, 'name' => $inputname, 'rows' => $lines, 'cols' => 60, 'class' => 'form-control')));
352
 
353
        $output .= html_writer::start_tag('div');
354
        if (count($formats) == 1) {
355
            reset($formats);
356
            $output .= html_writer::empty_tag('input', array('type' => 'hidden',
357
                    'name' => $inputname . 'format', 'value' => key($formats)));
358
 
359
        } else {
360
            $output .= html_writer::label(get_string('format'), 'menu' . $inputname . 'format', false);
361
            $output .= ' ';
362
            $output .= html_writer::select($formats, $inputname . 'format', $responseformat, '');
363
        }
364
        $output .= html_writer::end_tag('div');
365
 
366
        $output .= $this->filepicker_html($inputname, $draftitemid);
367
 
368
        $output .= html_writer::end_tag('div');
369
        return $output;
370
    }
371
 
372
    /**
373
     * Prepare the response for read-only display.
374
     * @param string $name the variable name this input edits.
375
     * @param question_attempt $qa the question attempt being display.
376
     * @param question_attempt_step $step the current step.
377
     * @param object $context the context the attempt belongs to.
378
     * @return string the response prepared for display.
379
     */
380
    protected function prepare_response($name, question_attempt $qa,
381
            question_attempt_step $step, $context) {
382
        if (!$step->has_qt_var($name)) {
383
            return '';
384
        }
385
 
386
        $formatoptions = new stdClass();
387
        $formatoptions->para = false;
388
        return format_text($step->get_qt_var($name), $step->get_qt_var($name . 'format'),
389
                $formatoptions);
390
    }
391
 
392
    /**
393
     * Prepare the response for editing.
394
     * @param string $name the variable name this input edits.
395
     * @param question_attempt_step $step the current step.
396
     * @param object $context the context the attempt belongs to.
397
     * @return string the response prepared for display.
398
     */
399
    protected function prepare_response_for_editing($name,
400
            question_attempt_step $step, $context) {
401
        return array(0, $step->get_qt_var($name));
402
    }
403
 
404
    /**
405
     * @param object $context the context the attempt belongs to.
406
     * @return array options for the editor.
407
     */
408
    protected function get_editor_options($context) {
409
        // Disable the text-editor autosave because quiz has it's own auto save function.
410
        return array('context' => $context, 'autosave' => false);
411
    }
412
 
413
    /**
414
     * @param object $context the context the attempt belongs to.
415
     * @param int $draftitemid draft item id.
416
     * @return array filepicker options for the editor.
417
     */
418
    protected function get_filepicker_options($context, $draftitemid) {
419
        return array('return_types'  => FILE_INTERNAL | FILE_EXTERNAL);
420
    }
421
 
422
    /**
423
     * @param string $inputname input field name.
424
     * @param int $draftitemid draft file area itemid.
425
     * @return string HTML for the filepicker, if used.
426
     */
427
    protected function filepicker_html($inputname, $draftitemid) {
428
        return '';
429
    }
430
}
431
 
432
 
433
/**
434
 * An essay format renderer for essays where the student should use the HTML
435
 * editor with the file picker.
436
 *
437
 * @copyright  2011 The Open University
438
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
439
 */
440
class qtype_essay_format_editorfilepicker_renderer extends qtype_essay_format_editor_renderer {
441
    protected function class_name() {
442
        return 'qtype_essay_editorfilepicker';
443
    }
444
 
445
    protected function prepare_response($name, question_attempt $qa,
446
            question_attempt_step $step, $context) {
447
        if (!$step->has_qt_var($name)) {
448
            return '';
449
        }
450
 
451
        $formatoptions = new stdClass();
452
        $formatoptions->para = false;
453
        $text = $qa->rewrite_response_pluginfile_urls($step->get_qt_var($name),
454
                $context->id, 'answer', $step);
455
        return format_text($text, $step->get_qt_var($name . 'format'), $formatoptions);
456
    }
457
 
458
    protected function prepare_response_for_editing($name,
459
            question_attempt_step $step, $context) {
460
        return $step->prepare_response_files_draft_itemid_with_text(
461
                $name, $context->id, $step->get_qt_var($name));
462
    }
463
 
464
    /**
465
     * Get editor options for question response text area.
466
     * @param object $context the context the attempt belongs to.
467
     * @return array options for the editor.
468
     */
469
    protected function get_editor_options($context) {
470
        return question_utils::get_editor_options($context);
471
    }
472
 
473
    /**
474
     * Get the options required to configure the filepicker for one of the editor
475
     * toolbar buttons.
476
     * @deprecated since 3.5
477
     * @param mixed $acceptedtypes array of types of '*'.
478
     * @param int $draftitemid the draft area item id.
479
     * @param object $context the context.
480
     * @return object the required options.
481
     */
482
    protected function specific_filepicker_options($acceptedtypes, $draftitemid, $context) {
483
        debugging('qtype_essay_format_editorfilepicker_renderer::specific_filepicker_options() is deprecated, ' .
484
            'use question_utils::specific_filepicker_options() instead.', DEBUG_DEVELOPER);
485
 
486
        $filepickeroptions = new stdClass();
487
        $filepickeroptions->accepted_types = $acceptedtypes;
488
        $filepickeroptions->return_types = FILE_INTERNAL | FILE_EXTERNAL;
489
        $filepickeroptions->context = $context;
490
        $filepickeroptions->env = 'filepicker';
491
 
492
        $options = initialise_filepicker($filepickeroptions);
493
        $options->context = $context;
494
        $options->client_id = uniqid();
495
        $options->env = 'editor';
496
        $options->itemid = $draftitemid;
497
 
498
        return $options;
499
    }
500
 
501
    /**
502
     * @param object $context the context the attempt belongs to.
503
     * @param int $draftitemid draft item id.
504
     * @return array filepicker options for the editor.
505
     */
506
    protected function get_filepicker_options($context, $draftitemid) {
507
        return question_utils::get_filepicker_options($context, $draftitemid);
508
    }
509
 
510
    protected function filepicker_html($inputname, $draftitemid) {
511
        $nonjspickerurl = new moodle_url('/repository/draftfiles_manager.php', array(
512
            'action' => 'browse',
513
            'env' => 'editor',
514
            'itemid' => $draftitemid,
515
            'subdirs' => false,
516
            'maxfiles' => -1,
517
            'sesskey' => sesskey(),
518
        ));
519
 
520
        return html_writer::empty_tag('input', array('type' => 'hidden',
521
                'name' => $inputname . ':itemid', 'value' => $draftitemid)) .
522
                html_writer::tag('noscript', html_writer::tag('div',
523
                    html_writer::tag('object', '', array('type' => 'text/html',
524
                        'data' => $nonjspickerurl, 'height' => 160, 'width' => 600,
525
                        'style' => 'border: 1px solid #000;'))));
526
    }
527
}
528
 
529
 
530
/**
531
 * An essay format renderer for essays where the student should use a plain
532
 * input box, but with a normal, proportional font.
533
 *
534
 * @copyright  2011 The Open University
535
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
536
 */
537
class qtype_essay_format_plain_renderer extends qtype_essay_format_renderer_base {
538
    /**
539
     * @return string the HTML for the textarea.
540
     */
541
    protected function textarea($response, $lines, $attributes) {
542
        $attributes['class'] = $this->class_name() . ' qtype_essay_response form-control';
543
        $attributes['rows'] = $lines;
544
        $attributes['cols'] = 60;
545
        return html_writer::tag('textarea', s($response), $attributes);
546
    }
547
 
548
    protected function class_name() {
549
        return 'qtype_essay_plain';
550
    }
551
 
552
    public function response_area_read_only($name, $qa, $step, $lines, $context) {
553
        $id = $qa->get_qt_field_name($name) . '_id';
554
 
555
        $responselabel = $this->displayoptions->add_question_identifier_to_label(get_string('answertext', 'qtype_essay'));
556
        $output = html_writer::tag('label', $responselabel, ['class' => 'sr-only', 'for' => $id]);
557
        $output .= $this->textarea($step->get_qt_var($name), $lines, ['id' => $id, 'readonly' => 'readonly']);
558
        return $output;
559
    }
560
 
561
    public function response_area_input($name, $qa, $step, $lines, $context) {
562
        $inputname = $qa->get_qt_field_name($name);
563
        $id = $inputname . '_id';
564
 
565
        $responselabel = $this->displayoptions->add_question_identifier_to_label(get_string('answertext', 'qtype_essay'));
566
        $output = html_writer::tag('label', $responselabel, ['class' => 'sr-only', 'for' => $id]);
567
        $output .= $this->textarea($step->get_qt_var($name), $lines, ['name' => $inputname, 'id' => $id]);
568
        $output .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => $inputname . 'format', 'value' => FORMAT_PLAIN]);
569
 
570
        return $output;
571
    }
572
}
573
 
574
 
575
/**
576
 * An essay format renderer for essays where the student should use a plain
577
 * input box with a monospaced font. You might use this, for example, for a
578
 * question where the students should type computer code.
579
 *
580
 * @copyright  2011 The Open University
581
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
582
 */
583
class qtype_essay_format_monospaced_renderer extends qtype_essay_format_plain_renderer {
584
    protected function class_name() {
585
        return 'qtype_essay_monospaced';
586
    }
587
}