diff --git a/question/engine/lib.php b/question/engine/lib.php index bd6fa348ad7..ee457eda4d9 100644 --- a/question/engine/lib.php +++ b/question/engine/lib.php @@ -466,6 +466,11 @@ class question_display_options { */ public $history = self::HIDDEN; + /** + * @var int the context the attempt being output belongs to. + */ + public $contextid; + /** * Set all the feedback-related fields {@link $feedback}, {@link generalfeedback}, * {@link rightanswer} and {@link manualcomment} to @@ -916,6 +921,7 @@ class question_usage_by_activity { * @return string HTML fragment representing the question. */ public function render_question($slot, $options, $number = null) { + $options->contextid = $this->context->id; return $this->get_question_attempt($slot)->render($options, $number); } @@ -926,6 +932,7 @@ class question_usage_by_activity { * @return string HTML fragment. */ public function render_question_head_html($slot) { + $options->contextid = $this->context->id; return $this->get_question_attempt($slot)->render_head_html(); } @@ -941,6 +948,7 @@ class question_usage_by_activity { * @return string HTML fragment representing the question. */ public function render_question_at_step($slot, $seq, $options, $number = null) { + $options->contextid = $this->context->id; return $this->get_question_attempt($slot)->render_at_step($seq, $options, $number, $this->preferredbehaviour); } @@ -1333,6 +1341,12 @@ class question_attempt { */ const PARAM_MARK = 'parammark'; + /** + * @var string special value used by manual grading because {@link PARAM_NUMBER} + * converts '' to 0. + */ + const PARAM_FILES = 'paramfiles'; + /** @var integer if this attempts is stored in the question_attempts table, the id of that row. */ protected $id = null; @@ -1666,6 +1680,39 @@ class question_attempt { return $default; } + /** + * Get the latest set of files for a particular question type variable of + * type question_attempt::PARAM_FILES. + * + * @param string $name the name of the associated variable. + * @return array of {@link stored_files}. + */ + public function get_last_qt_files($name, $contextid) { + foreach ($this->get_reverse_step_iterator() as $step) { + if ($step->has_qt_var($name)) { + return $step->get_qt_files($name, $contextid); + } + } + return array(); + } + + /** + * Get the URL of a file that belongs to a response variable of this + * question_attempt. + * @param stored_file $file the file to link to. + * @return string the URL of that file. + */ + public function get_response_file_url(stored_file $file) { + return file_encode_url(new moodle_url('/pluginfile.php'), '/' . implode('/', array( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + $this->usageid, + $this->slot, + $file->get_itemid())) . + $file->get_filepath() . $file->get_filename()); + } + /** * Get the latest value of a particular behaviour variable. That is, * get the value from the latest step that has it set. Return null if it is @@ -1997,9 +2044,11 @@ class question_attempt { } else { $var = null; } + if (is_string($var)) { - $var = stripslashes($var); + $var = $var; } + return $var; } @@ -2488,6 +2537,9 @@ class question_attempt_step { /** @var array name => value pairs. The submitted data. */ private $data; + /** @var array name => array of {@link stored_file}s. Caches the contents of file areas. */ + private $files = array(); + /** * You should not need to call this constructor in your own code. Steps are * normally created by {@link question_attempt} methods like @@ -2582,6 +2634,30 @@ class question_attempt_step { $this->data[$name] = $value; } + /** + * Get the latest set of files for a particular question type variable of + * type question_attempt::PARAM_FILES. + * + * @param string $name the name of the associated variable. + * @return array of {@link stored_files}. + */ + public function get_qt_files($name, $contextid) { + if (array_key_exists($name, $this->files)) { + return $this->files[$name]; + } + + if (!$this->has_qt_var($name)) { + $this->files[$name] = array(); + return array(); + } + + $fs = get_file_storage(); + $this->files[$name] = $fs->get_area_files($contextid, 'question', + 'response_' . $name, $this->id, 'sortorder', false); + + return $this->files[$name]; + } + /** * Get all the question type variables. * @param array name => value pairs. @@ -2697,6 +2773,7 @@ class question_attempt_step { $step = new question_attempt_step_read_only($data, $record->timecreated, $record->userid); $step->state = question_state::get($record->state); + $step->id = $record->attemptstepid; if (!is_null($record->fraction)) { $step->fraction = $record->fraction + 0; } diff --git a/question/type/essay/question.php b/question/type/essay/question.php index 733a78c6e83..8e443ae5fed 100644 --- a/question/type/essay/question.php +++ b/question/type/essay/question.php @@ -55,7 +55,11 @@ class qtype_essay_question extends question_with_responses { } public function get_expected_data() { - return array('answer' => PARAM_CLEANHTML); + $expecteddata = array('answer' => PARAM_CLEANHTML); + if ($this->attachments != 0) { + $expecteddata['attachments'] = question_attempt::PARAM_FILES; + } + return $expecteddata; } public function summarise_response(array $response) { @@ -80,4 +84,14 @@ class qtype_essay_question extends question_with_responses { return question_utils::arrays_same_at_key_missing_is_blank( $prevresponse, $newresponse, 'answer'); } + + public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) { + if ($component == 'question' && $filearea == 'response_attachments') { + // Response attachments visible if the question has them. + return $this->attachments != 0; + + } else { + return parent::check_file_access($qa, $options, $component, $filearea, $args, $forcedownload); + } + } } diff --git a/question/type/essay/renderer.php b/question/type/essay/renderer.php index a61fb9d6693..657dbf332e5 100644 --- a/question/type/essay/renderer.php +++ b/question/type/essay/renderer.php @@ -45,7 +45,7 @@ class qtype_essay_renderer extends qtype_renderer { $inputname = $qa->get_qt_field_name('answer'); $response = $qa->get_last_qt_var('answer', ''); if (empty($options->readonly)) { - $answer =$responseoutput->response_area_input($inputname, + $answer = $responseoutput->response_area_input($inputname, $response, $question->responsefieldlines); } else { @@ -56,13 +56,13 @@ class qtype_essay_renderer extends qtype_renderer { $files = ''; if (empty($options->readonly)) { if ($question->attachments == 1) { - $files = $this->filepicker_input(); + $files = $this->filepicker_input($qa, $options); } else if ($question->attachments != 0) { - $files = $this->filemanager_input(); + $files = $this->filemanager_input($qa, $options); } } else if ($question->attachments != 0) { - $files = $this->files_read_only(); + $files = $this->files_read_only($qa, $options); } $result = ''; @@ -71,6 +71,7 @@ class qtype_essay_renderer extends qtype_renderer { $result .= html_writer::start_tag('div', array('class' => 'ablock')); $result .= html_writer::tag('div', $answer, array('class' => 'answer')); + $result .= html_writer::tag('div', $files, array('class' => 'attachments')); $result .= html_writer::end_tag('div'); return $result; @@ -79,21 +80,30 @@ class qtype_essay_renderer extends qtype_renderer { /** * Displays any attached files when the question is in read-only mode. */ - public function files_read_only() { - return ''; + public function files_read_only(question_attempt $qa, question_display_options $options) { + $files = $qa->get_last_qt_files('attachments', $options->contextid); + $output = array(); + + foreach ($files as $file) { + $mimetype = $file->get_mimetype(); + $output[] = html_writer::tag('p', html_writer::link($qa->get_response_file_url($file), + $this->output->pix_icon(file_mimetype_icon($mimetype), $mimetype, + 'moodle', array('class' => 'icon')) . ' ' . s($file->get_filename()))); + } + return implode($output); } /** * Displays the input control for when the student should upload a single file. */ - public function filepicker_input() { + public function filepicker_input(question_attempt $qa, question_display_options $options) { return ''; } /** * Displays the input control for when the student should upload a number of files. */ - public function filemanager_input() { + public function filemanager_input(question_attempt $qa, question_display_options $options) { return ''; } } diff --git a/question/type/essay/styles.css b/question/type/essay/styles.css index 8e84dee7ada..cf1c8c667c8 100644 --- a/question/type/essay/styles.css +++ b/question/type/essay/styles.css @@ -11,4 +11,5 @@ } .que.essay .qtype_essay_response.qtype_essay_editor { background-color: white; + min-height: 3em; }