MDL-20636 Essay questions can now handle files in the HTML editor. #216
[moodle.git] / question / engine / lib.php
index c517660..141edfc 100644 (file)
@@ -1347,6 +1347,12 @@ class question_attempt {
      */
     const PARAM_FILES = 'paramfiles';
 
+    /**
+     * @var string special value to indicate a response variable that is uploaded
+     * files.
+     */
+    const PARAM_CLEANHTML_FILES = 'paramcleanhtmlfiles';
+
     /** @var integer if this attempts is stored in the question_attempts table, the id of that row. */
     protected $id = null;
 
@@ -1661,6 +1667,21 @@ class question_attempt {
         return $default;
     }
 
+    /**
+     * Get the last step with a particular question type varialbe set.
+     * @param string $name the name of the variable to get.
+     * @return question_attempt_step the last step, or a step with no variables
+     * if there was not a real step.
+     */
+    public function get_last_step_with_qt_var($name) {
+        foreach ($this->get_reverse_step_iterator() as $step) {
+            if ($step->has_qt_var($name)) {
+                return $step;
+            }
+        }
+        return new question_attempt_step_read_only();
+    }
+
     /**
      * Get the latest value of a particular question type variable. That is, get
      * the value from the latest step that has it set. Return null if it is not
@@ -1672,12 +1693,12 @@ class question_attempt {
      * @return mixed string value, or $default if it has never been set.
      */
     public function get_last_qt_var($name, $default = null) {
-        foreach ($this->get_reverse_step_iterator() as $step) {
-            if ($step->has_qt_var($name)) {
-                return $step->get_qt_var($name);
-            }
+        $step = $this->get_last_step_with_qt_var($name);
+        if ($step->has_qt_var($name)) {
+            return $step->get_qt_var($name);
+        } else {
+            return $default;
         }
-        return $default;
     }
 
     /**
@@ -1890,6 +1911,15 @@ class question_attempt {
         return $this->behaviour->summarise_action($step);
     }
 
+    /**
+     * Helper function used by {@link rewrite_pluginfile_urls()} and
+     * {@link rewrite_response_pluginfile_urls()}.
+     * @return array ids that need to go into the file paths.
+     */
+    protected function extra_file_path_components() {
+        return array($this->get_usage_id(), $this->get_slot());
+    }
+
     /**
      * Calls {@link question_rewrite_question_urls()} with appropriate parameters
      * for content belonging to this question.
@@ -1897,11 +1927,28 @@ class question_attempt {
      * @param string $component the component name (normally 'question' or 'qtype_...')
      * @param string $filearea the name of the file area.
      * @param int $itemid the item id.
+     * @return srting the content with the URLs rewritten.
      */
     public function rewrite_pluginfile_urls($text, $component, $filearea, $itemid) {
-        return question_rewrite_question_urls($text,
-                'pluginfile.php', $this->question->contextid, $component, $filearea,
-                array($this->get_usage_id(), $this->get_slot()), $itemid);
+        return question_rewrite_question_urls($text, 'pluginfile.php',
+                $this->question->contextid, $component, $filearea,
+                $this->extra_file_path_components(), $itemid);
+    }
+
+    /**
+     * Calls {@link question_rewrite_question_urls()} with appropriate parameters
+     * for content belonging to responses to this question.
+     *
+     * @param string $text the text to update the URLs in.
+     * @param int $contextid the id of the context the quba belongs to.
+     * @param string $name the variable name the files belong to.
+     * @param question_attempt_step $step the step the response is coming from.
+     * @return srting the content with the URLs rewritten.
+     */
+    public function rewrite_response_pluginfile_urls($text, $contextid, $name,
+            question_attempt_step $step) {
+        return $step->rewrite_response_pluginfile_urls($text, $contextid, $name,
+                $this->extra_file_path_components());
     }
 
     /**
@@ -2043,61 +2090,66 @@ class question_attempt {
      * {@link optional_param()}, except that the results is returned without
      * slashes.
      * @param string $name the paramter name.
-     * @param int $type one of the PARAM_... constants.
+     * @param int $type one of the standard PARAM_... constants, or one of the
+     *      special extra constands defined by this class.
      * @param array $postdata (optional, only inteded for testing use) take the
      *      data from this array, instead of from $_POST.
      * @return mixed the requested value.
      */
     public function get_submitted_var($name, $type, $postdata = null) {
-        // Special case to work around PARAM_NUMBER converting '' to 0.
-        if ($type == self::PARAM_MARK) {
-            $mark = $this->get_submitted_var($name, PARAM_RAW_TRIMMED, $postdata);
-            if ($mark === '') {
-                return $mark;
-            } else {
-                return $this->get_submitted_var($name, PARAM_NUMBER, $postdata);
-            }
-        }
+        switch ($type) {
+            case self::PARAM_MARK:
+                // Special case to work around PARAM_NUMBER converting '' to 0.
+                $mark = $this->get_submitted_var($name, PARAM_RAW_TRIMMED, $postdata);
+                if ($mark === '') {
+                    return $mark;
+                } else {
+                    return $this->get_submitted_var($name, PARAM_NUMBER, $postdata);
+                }
 
-        if ($type == self::PARAM_FILES) {
-            return $this->process_response_files($name, $postdata);
-        }
+            case self::PARAM_FILES:
+                return $this->process_response_files($name, $name, $postdata);
 
-        if (is_null($postdata)) {
-            $var = optional_param($name, null, $type);
-        } else if (array_key_exists($name, $postdata)) {
-            $var = clean_param($postdata[$name], $type);
-        } else {
-            $var = null;
-        }
+            case self::PARAM_CLEANHTML_FILES:
+                $var = $this->get_submitted_var($name, PARAM_CLEANHTML, $postdata);
+                return $this->process_response_files($name, $name . ':itemid', $postdata, $var);
 
-        if (is_string($var)) {
-            $var = $var;
-        }
+            default:
+                if (is_null($postdata)) {
+                    $var = optional_param($name, null, $type);
+                } else if (array_key_exists($name, $postdata)) {
+                    $var = clean_param($postdata[$name], $type);
+                } else {
+                    $var = null;
+                }
 
-        return $var;
+                return $var;
+        }
     }
 
     /**
      * Handle a submitted variable representing uploaded files.
      * @param string $name the field name.
+     * @param string $draftidname the field name holding the draft file area id.
      * @param array $postdata (optional, only inteded for testing use) take the
      *      data from this array, instead of from $_POST. At the moment, this
      *      behaves as if there were no files.
+     * @param string $text optional reponse text.
+     * @return question_file_saver that can be used to save the files later.
      */
-    protected function process_response_files($name, $postdata = null) {
+    protected function process_response_files($name, $draftidname, $postdata = null, $text = null) {
         if ($postdata) {
             // There can be no files with test data (at the moment).
             return null;
         }
 
-        $draftitemid = file_get_submitted_draft_itemid($name);
+        $draftitemid = file_get_submitted_draft_itemid($draftidname);
         if (!$draftitemid) {
             return null;
         }
 
         return new question_file_saver($draftitemid, 'question', 'response_' .
-                str_replace($this->get_field_prefix(), '', $name));
+                str_replace($this->get_field_prefix(), '', $name), $text);
     }
 
     /**
@@ -2612,10 +2664,6 @@ class question_attempt_step {
         }
     }
 
-    public function get_id() {
-        return $this->id; // TODO get rid of this.
-    }
-
     /** @return question_state The state after this step. */
     public function get_state() {
         return $this->state;
@@ -2719,12 +2767,44 @@ class question_attempt_step {
      * @return int the draft itemid.
      */
     public function prepare_response_files_draft_itemid($name, $contextid) {
-    $draftid = 0; // Will be filled in by file_prepare_draft_area.
-        file_prepare_draft_area($draftid, $contextid, 'question',
-                'response_' . $name, $this->id);
+        list($draftid, $notused) = $this->prepare_response_files_draft_itemid_with_text(
+                $name, $contextid, null);
         return $draftid;
     }
 
+    /**
+     * Prepare a draft file are for the files belonging the a response variable
+     * of this step, while rewriting the URLs in some text.
+     *
+     * @param string $name the variable name the files belong to.
+     * @param int $contextid the id of the context the quba belongs to.
+     * @param string $text the text to update the URLs in.
+     * @return array(int, string) the draft itemid and the text with URLs rewritten.
+     */
+    public function prepare_response_files_draft_itemid_with_text($name, $contextid, $text) {
+        $draftid = 0; // Will be filled in by file_prepare_draft_area.
+        $newtext = file_prepare_draft_area($draftid, $contextid, 'question',
+                'response_' . $name, $this->id, null, $text);
+        return array($draftid, $newtext);
+    }
+
+    /**
+     * Rewrite the @@PLUGINFILE@@ tokens in a response variable from this step
+     * that contains links to file. Normally you should probably call
+     * {@link question_attempt::rewrite_response_pluginfile_urls()} instead of
+     * calling this method directly.
+     *
+     * @param string $text the text to update the URLs in.
+     * @param int $contextid the id of the context the quba belongs to.
+     * @param string $name the variable name the files belong to.
+     * @param array $extra extra file path components.
+     * @return string the rewritten text.
+     */
+    public function rewrite_response_pluginfile_urls($text, $contextid, $name, $extras) {
+        return question_rewrite_question_urls($text, 'pluginfile.php', $contextid,
+                'question', 'response_' . $name, $extras, $this->id);
+    }
+
     /**
      * Get all the question type variables.
      * @param array name => value pairs.