MDL-20636 Can now display the attachments to an essay question that has attachments.
authorTim Hunt <T.J.Hunt@open.ac.uk>
Fri, 11 Mar 2011 17:30:13 +0000 (17:30 +0000)
committerTim Hunt <T.J.Hunt@open.ac.uk>
Thu, 31 Mar 2011 11:44:47 +0000 (12:44 +0100)
question/engine/lib.php
question/type/essay/question.php
question/type/essay/renderer.php
question/type/essay/styles.css

index bd6fa34..ee457ed 100644 (file)
@@ -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;
         }
index 733a78c..8e443ae 100644 (file)
@@ -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);
+        }
+    }
 }
index a61fb9d..657dbf3 100644 (file)
@@ -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 '';
     }
 }
index 8e84dee..cf1c8c6 100644 (file)
@@ -11,4 +11,5 @@
 }
 .que.essay .qtype_essay_response.qtype_essay_editor {
     background-color: white;
+    min-height: 3em;
 }