MDL-70373 qtype_essay: missing border on atto input
[moodle.git] / question / type / essay / renderer.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  * 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  */
27 defined('MOODLE_INTERNAL') || die();
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) {
40         $question = $qa->get_question();
41         $responseoutput = $question->get_format_renderer($this->page);
43         // Answer field.
44         $step = $qa->get_last_step_with_qt_var('answer');
46         if (!$step->has_qt_var('answer') && empty($options->readonly)) {
47             // Question has never been answered, fill it with response template.
48             $step = new question_attempt_step(array('answer' => $question->responsetemplate));
49         }
51         if (empty($options->readonly)) {
52             $answer = $responseoutput->response_area_input('answer', $qa,
53                     $step, $question->responsefieldlines, $options->context);
55         } else {
56             $answer = $responseoutput->response_area_read_only('answer', $qa,
57                     $step, $question->responsefieldlines, $options->context);
58         }
60         $files = '';
61         if ($question->attachments) {
62             if (empty($options->readonly)) {
63                 $files = $this->files_input($qa, $question->attachments, $options);
65             } else {
66                 $files = $this->files_read_only($qa, $options);
67             }
68         }
70         $result = '';
71         $result .= html_writer::tag('div', $question->format_questiontext($qa),
72                 array('class' => 'qtext'));
74         $result .= html_writer::start_tag('div', array('class' => 'ablock'));
75         $result .= html_writer::tag('div', $answer, array('class' => 'answer'));
76         $result .= html_writer::tag('div', $files, array('class' => 'attachments'));
77         $result .= html_writer::end_tag('div');
79         return $result;
80     }
82     /**
83      * Displays any attached files when the question is in read-only mode.
84      * @param question_attempt $qa the question attempt to display.
85      * @param question_display_options $options controls what should and should
86      *      not be displayed. Used to get the context.
87      */
88     public function files_read_only(question_attempt $qa, question_display_options $options) {
89         $files = $qa->get_last_qt_files('attachments', $options->context->id);
90         $output = array();
92         foreach ($files as $file) {
93             $output[] = html_writer::tag('p', html_writer::link($qa->get_response_file_url($file),
94                     $this->output->pix_icon(file_file_icon($file), get_mimetype_description($file),
95                     'moodle', array('class' => 'icon')) . ' ' . s($file->get_filename())));
96         }
97         return implode($output);
98     }
100     /**
101      * Displays the input control for when the student should upload a single file.
102      * @param question_attempt $qa the question attempt to display.
103      * @param int $numallowed the maximum number of attachments allowed. -1 = unlimited.
104      * @param question_display_options $options controls what should and should
105      *      not be displayed. Used to get the context.
106      */
107     public function files_input(question_attempt $qa, $numallowed,
108             question_display_options $options) {
109         global $CFG, $COURSE;
110         require_once($CFG->dirroot . '/lib/form/filemanager.php');
112         $pickeroptions = new stdClass();
113         $pickeroptions->mainfile = null;
114         $pickeroptions->maxfiles = $numallowed;
115         $pickeroptions->itemid = $qa->prepare_response_files_draft_itemid(
116                 'attachments', $options->context->id);
117         $pickeroptions->context = $options->context;
118         $pickeroptions->return_types = FILE_INTERNAL | FILE_CONTROLLED_LINK;
120         $pickeroptions->itemid = $qa->prepare_response_files_draft_itemid(
121                 'attachments', $options->context->id);
122         $pickeroptions->accepted_types = $qa->get_question()->filetypeslist;
124         $fm = new form_filemanager($pickeroptions);
125         $fm->options->maxbytes = get_user_max_upload_file_size(
126             $this->page->context,
127             $CFG->maxbytes,
128             $COURSE->maxbytes,
129             $qa->get_question()->maxbytes
130         );
131         $filesrenderer = $this->page->get_renderer('core', 'files');
133         $text = '';
134         if (!empty($qa->get_question()->filetypeslist)) {
135             $text = html_writer::tag('p', get_string('acceptedfiletypes', 'qtype_essay'));
136             $filetypesutil = new \core_form\filetypes_util();
137             $filetypes = $qa->get_question()->filetypeslist;
138             $filetypedescriptions = $filetypesutil->describe_file_types($filetypes);
139             $text .= $this->render_from_template('core_form/filetypes-descriptions', $filetypedescriptions);
140         }
141         return $filesrenderer->render($fm). html_writer::empty_tag(
142                 'input', array('type' => 'hidden', 'name' => $qa->get_qt_field_name('attachments'),
143                 'value' => $pickeroptions->itemid)) . $text;
144     }
146     public function manual_comment(question_attempt $qa, question_display_options $options) {
147         if ($options->manualcomment != question_display_options::EDITABLE) {
148             return '';
149         }
151         $question = $qa->get_question();
152         return html_writer::nonempty_tag('div', $question->format_text(
153                 $question->graderinfo, $question->graderinfo, $qa, 'qtype_essay',
154                 'graderinfo', $question->id), array('class' => 'graderinfo'));
155     }
159 /**
160  * A base class to abstract out the differences between different type of
161  * response format.
162  *
163  * @copyright  2011 The Open University
164  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
165  */
166 abstract class qtype_essay_format_renderer_base extends plugin_renderer_base {
167     /**
168      * Render the students respone when the question is in read-only mode.
169      * @param string $name the variable name this input edits.
170      * @param question_attempt $qa the question attempt being display.
171      * @param question_attempt_step $step the current step.
172      * @param int $lines approximate size of input box to display.
173      * @param object $context the context teh output belongs to.
174      * @return string html to display the response.
175      */
176     public abstract function response_area_read_only($name, question_attempt $qa,
177             question_attempt_step $step, $lines, $context);
179     /**
180      * Render the students respone when the question is in read-only mode.
181      * @param string $name the variable name this input edits.
182      * @param question_attempt $qa the question attempt being display.
183      * @param question_attempt_step $step the current step.
184      * @param int $lines approximate size of input box to display.
185      * @param object $context the context teh output belongs to.
186      * @return string html to display the response for editing.
187      */
188     public abstract function response_area_input($name, question_attempt $qa,
189             question_attempt_step $step, $lines, $context);
191     /**
192      * @return string specific class name to add to the input element.
193      */
194     protected abstract function class_name();
197 /**
198  * An essay format renderer for essays where the student should not enter
199  * any inline response.
200  *
201  * @copyright  2013 Binghamton University
202  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
203  */
204 class qtype_essay_format_noinline_renderer extends plugin_renderer_base {
206     protected function class_name() {
207         return 'qtype_essay_noinline';
208     }
210     public function response_area_read_only($name, $qa, $step, $lines, $context) {
211         return '';
212     }
214     public function response_area_input($name, $qa, $step, $lines, $context) {
215         return '';
216     }
220 /**
221  * An essay format renderer for essays where the student should use the HTML
222  * editor without the file picker.
223  *
224  * @copyright  2011 The Open University
225  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
226  */
227 class qtype_essay_format_editor_renderer extends plugin_renderer_base {
228     protected function class_name() {
229         return 'qtype_essay_editor';
230     }
232     public function response_area_read_only($name, $qa, $step, $lines, $context) {
233         return html_writer::tag('div', $this->prepare_response($name, $qa, $step, $context),
234                 ['class' => $this->class_name() . ' qtype_essay_response readonly',
235                         'style' => 'min-height: ' . ($lines * 1.5) . 'em;']);
236         // Height $lines * 1.5 because that is a typical line-height on web pages.
237         // That seems to give results that look OK.
238     }
240     public function response_area_input($name, $qa, $step, $lines, $context) {
241         global $CFG;
242         require_once($CFG->dirroot . '/repository/lib.php');
244         $inputname = $qa->get_qt_field_name($name);
245         $responseformat = $step->get_qt_var($name . 'format');
246         $id = $inputname . '_id';
248         $editor = editors_get_preferred_editor($responseformat);
249         $strformats = format_text_menu();
250         $formats = $editor->get_supported_formats();
251         foreach ($formats as $fid) {
252             $formats[$fid] = $strformats[$fid];
253         }
255         list($draftitemid, $response) = $this->prepare_response_for_editing(
256                 $name, $step, $context);
258         $editor->set_text($response);
259         $editor->use_editor($id, $this->get_editor_options($context),
260                 $this->get_filepicker_options($context, $draftitemid));
262         $output = '';
263         $output .= html_writer::start_tag('div', array('class' =>
264                 $this->class_name() . ' qtype_essay_response'));
266         $output .= html_writer::tag('div', html_writer::tag('textarea', s($response),
267                 array('id' => $id, 'name' => $inputname, 'rows' => $lines, 'cols' => 60, 'class' => 'form-control')));
269         $output .= html_writer::start_tag('div');
270         if (count($formats) == 1) {
271             reset($formats);
272             $output .= html_writer::empty_tag('input', array('type' => 'hidden',
273                     'name' => $inputname . 'format', 'value' => key($formats)));
275         } else {
276             $output .= html_writer::label(get_string('format'), 'menu' . $inputname . 'format', false);
277             $output .= ' ';
278             $output .= html_writer::select($formats, $inputname . 'format', $responseformat, '');
279         }
280         $output .= html_writer::end_tag('div');
282         $output .= $this->filepicker_html($inputname, $draftitemid);
284         $output .= html_writer::end_tag('div');
285         return $output;
286     }
288     /**
289      * Prepare the response for read-only display.
290      * @param string $name the variable name this input edits.
291      * @param question_attempt $qa the question attempt being display.
292      * @param question_attempt_step $step the current step.
293      * @param object $context the context the attempt belongs to.
294      * @return string the response prepared for display.
295      */
296     protected function prepare_response($name, question_attempt $qa,
297             question_attempt_step $step, $context) {
298         if (!$step->has_qt_var($name)) {
299             return '';
300         }
302         $formatoptions = new stdClass();
303         $formatoptions->para = false;
304         return format_text($step->get_qt_var($name), $step->get_qt_var($name . 'format'),
305                 $formatoptions);
306     }
308     /**
309      * Prepare the response for editing.
310      * @param string $name the variable name this input edits.
311      * @param question_attempt_step $step the current step.
312      * @param object $context the context the attempt belongs to.
313      * @return string the response prepared for display.
314      */
315     protected function prepare_response_for_editing($name,
316             question_attempt_step $step, $context) {
317         return array(0, $step->get_qt_var($name));
318     }
320     /**
321      * @param object $context the context the attempt belongs to.
322      * @return array options for the editor.
323      */
324     protected function get_editor_options($context) {
325         // Disable the text-editor autosave because quiz has it's own auto save function.
326         return array('context' => $context, 'autosave' => false);
327     }
329     /**
330      * @param object $context the context the attempt belongs to.
331      * @param int $draftitemid draft item id.
332      * @return array filepicker options for the editor.
333      */
334     protected function get_filepicker_options($context, $draftitemid) {
335         return array('return_types'  => FILE_INTERNAL | FILE_EXTERNAL);
336     }
338     /**
339      * @param string $inputname input field name.
340      * @param int $draftitemid draft file area itemid.
341      * @return string HTML for the filepicker, if used.
342      */
343     protected function filepicker_html($inputname, $draftitemid) {
344         return '';
345     }
349 /**
350  * An essay format renderer for essays where the student should use the HTML
351  * editor with the file picker.
352  *
353  * @copyright  2011 The Open University
354  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
355  */
356 class qtype_essay_format_editorfilepicker_renderer extends qtype_essay_format_editor_renderer {
357     protected function class_name() {
358         return 'qtype_essay_editorfilepicker';
359     }
361     protected function prepare_response($name, question_attempt $qa,
362             question_attempt_step $step, $context) {
363         if (!$step->has_qt_var($name)) {
364             return '';
365         }
367         $formatoptions = new stdClass();
368         $formatoptions->para = false;
369         $text = $qa->rewrite_response_pluginfile_urls($step->get_qt_var($name),
370                 $context->id, 'answer', $step);
371         return format_text($text, $step->get_qt_var($name . 'format'), $formatoptions);
372     }
374     protected function prepare_response_for_editing($name,
375             question_attempt_step $step, $context) {
376         return $step->prepare_response_files_draft_itemid_with_text(
377                 $name, $context->id, $step->get_qt_var($name));
378     }
380     /**
381      * Get editor options for question response text area.
382      * @param object $context the context the attempt belongs to.
383      * @return array options for the editor.
384      */
385     protected function get_editor_options($context) {
386         return question_utils::get_editor_options($context);
387     }
389     /**
390      * Get the options required to configure the filepicker for one of the editor
391      * toolbar buttons.
392      * @deprecated since 3.5
393      * @param mixed $acceptedtypes array of types of '*'.
394      * @param int $draftitemid the draft area item id.
395      * @param object $context the context.
396      * @return object the required options.
397      */
398     protected function specific_filepicker_options($acceptedtypes, $draftitemid, $context) {
399         debugging('qtype_essay_format_editorfilepicker_renderer::specific_filepicker_options() is deprecated, ' .
400             'use question_utils::specific_filepicker_options() instead.', DEBUG_DEVELOPER);
402         $filepickeroptions = new stdClass();
403         $filepickeroptions->accepted_types = $acceptedtypes;
404         $filepickeroptions->return_types = FILE_INTERNAL | FILE_EXTERNAL;
405         $filepickeroptions->context = $context;
406         $filepickeroptions->env = 'filepicker';
408         $options = initialise_filepicker($filepickeroptions);
409         $options->context = $context;
410         $options->client_id = uniqid();
411         $options->env = 'editor';
412         $options->itemid = $draftitemid;
414         return $options;
415     }
417     /**
418      * @param object $context the context the attempt belongs to.
419      * @param int $draftitemid draft item id.
420      * @return array filepicker options for the editor.
421      */
422     protected function get_filepicker_options($context, $draftitemid) {
423         return question_utils::get_filepicker_options($context, $draftitemid);
424     }
426     protected function filepicker_html($inputname, $draftitemid) {
427         $nonjspickerurl = new moodle_url('/repository/draftfiles_manager.php', array(
428             'action' => 'browse',
429             'env' => 'editor',
430             'itemid' => $draftitemid,
431             'subdirs' => false,
432             'maxfiles' => -1,
433             'sesskey' => sesskey(),
434         ));
436         return html_writer::empty_tag('input', array('type' => 'hidden',
437                 'name' => $inputname . ':itemid', 'value' => $draftitemid)) .
438                 html_writer::tag('noscript', html_writer::tag('div',
439                     html_writer::tag('object', '', array('type' => 'text/html',
440                         'data' => $nonjspickerurl, 'height' => 160, 'width' => 600,
441                         'style' => 'border: 1px solid #000;'))));
442     }
446 /**
447  * An essay format renderer for essays where the student should use a plain
448  * input box, but with a normal, proportional font.
449  *
450  * @copyright  2011 The Open University
451  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
452  */
453 class qtype_essay_format_plain_renderer extends plugin_renderer_base {
454     /**
455      * @return string the HTML for the textarea.
456      */
457     protected function textarea($response, $lines, $attributes) {
458         $attributes['class'] = $this->class_name() . ' qtype_essay_response form-control';
459         $attributes['rows'] = $lines;
460         $attributes['cols'] = 60;
461         return html_writer::tag('textarea', s($response), $attributes);
462     }
464     protected function class_name() {
465         return 'qtype_essay_plain';
466     }
468     public function response_area_read_only($name, $qa, $step, $lines, $context) {
469         return $this->textarea($step->get_qt_var($name), $lines, array('readonly' => 'readonly'));
470     }
472     public function response_area_input($name, $qa, $step, $lines, $context) {
473         $inputname = $qa->get_qt_field_name($name);
474         return $this->textarea($step->get_qt_var($name), $lines, array('name' => $inputname)) .
475                 html_writer::empty_tag('input', array('type' => 'hidden',
476                     'name' => $inputname . 'format', 'value' => FORMAT_PLAIN));
477     }
481 /**
482  * An essay format renderer for essays where the student should use a plain
483  * input box with a monospaced font. You might use this, for example, for a
484  * question where the students should type computer code.
485  *
486  * @copyright  2011 The Open University
487  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
488  */
489 class qtype_essay_format_monospaced_renderer extends qtype_essay_format_plain_renderer {
490     protected function class_name() {
491         return 'qtype_essay_monospaced';
492     }