From 521a6ab04d06e5ffdb444ea766ccbf352e622aab Mon Sep 17 00:00:00 2001
From: Penny Leach <penny@liip.ch>
Date: Tue, 15 Dec 2009 12:36:49 +0000
Subject: [PATCH] portfolio MDL-20896 added the ability to set mime info for
 "intended" files

This means for places in Moodle that are going to write a file, like a
CSV file, they can set the intended mimetype of the generated file.

Previously you had to use a stored_file object.

This also gets rid of portfolio_fake_add_url function and replaces the
data module implementation with a button.  I also refactored
portfolio_add_button::to_html to use moodle_url so it's easy to return
the same parameters to hidden form fields, an escaped url (for a link),
and a non escaped url (to redirect to, which is what the data module
does)
---
 lang/en_utf8/portfolio.php  |   1 -
 lib/portfolio/caller.php    |  13 ++++
 lib/portfolio/constants.php |   5 ++
 lib/portfoliolib.php        | 134 +++++++++++++++++++-----------------
 mod/data/export.php         |  11 ++-
 mod/data/export_form.php    |  18 +++--
 mod/data/locallib.php       |   4 +-
 portfolio/add.php           |   2 +-
 8 files changed, 110 insertions(+), 78 deletions(-)

diff --git a/lang/en_utf8/portfolio.php b/lang/en_utf8/portfolio.php
index a18e3d72968..42dca51ba5c 100644
--- a/lang/en_utf8/portfolio.php
+++ b/lang/en_utf8/portfolio.php
@@ -83,7 +83,6 @@ $string['instancenotdelete'] = 'Failed to delete portfolio';
 $string['instancesaved'] = 'Portfolio saved successfully';
 $string['invalidaddformat'] = 'Invalid add format passed to portfolio_add_button. ($a) Must be one of PORTFOLIO_ADD_XXX';
 $string['invalidtempid'] = 'Invalid export id. maybe it has expired';
-$string['invalidfileargument'] = 'Invalid file argument passed to portfolio_format_from_file - must be stored_file object';
 $string['invalidfileareaargs'] = 'Invalid file area arguments passed to set_file_and_format_data - must contain contextid, filearea and itemid';
 $string['invalidsha1file'] = 'Invalid call to get_sha1_file - either single or multifiles must be set';
 $string['invalidpreparepackagefile'] = 'Invalid call to prepare_package_file - either single or multifiles must be set';
diff --git a/lib/portfolio/caller.php b/lib/portfolio/caller.php
index ae600aad4b2..431a8aa6f61 100644
--- a/lib/portfolio/caller.php
+++ b/lib/portfolio/caller.php
@@ -74,6 +74,11 @@ abstract class portfolio_caller_base {
     */
     protected $multifiles;
 
+    /**
+     * set this for generated-file exports
+     */
+    protected $intendedmimetype;
+
     public function __construct($callbackargs) {
         $expected = call_user_func(array(get_class($this), 'expected_callbackargs'));
         foreach ($expected as $key => $required) {
@@ -473,6 +478,14 @@ abstract class portfolio_caller_base {
         $this->supportedformats[] = $format;
     }
 
+    public function get_mimetype() {
+        if ($this->singlefile instanceof stored_file) {
+            return $this->singlefile->get_mimetype();
+        } else if (!empty($this->intendedmimetype)) {
+            return $this->intendedmimetype;
+        }
+    }
+
     /**
      * array of arguments the caller expects to be passed through to it
      * this must be keyed on the argument name, and the array value is a boolean,
diff --git a/lib/portfolio/constants.php b/lib/portfolio/constants.php
index 1492cea6516..c1c744af2a4 100644
--- a/lib/portfolio/constants.php
+++ b/lib/portfolio/constants.php
@@ -213,3 +213,8 @@ define('PORTFOLIO_ADD_ICON_LINK', 3);
 */
 define('PORTFOLIO_ADD_TEXT_LINK', 4);
 
+/**
+ * hacky way to turn the button class into a url to redirect to
+ * this replaces the old portfolio_fake_add_url function
+ */
+define('PORTFOLIO_ADD_FAKE_URL', 5);
diff --git a/lib/portfoliolib.php b/lib/portfoliolib.php
index 44f961678c6..0558866f777 100644
--- a/lib/portfoliolib.php
+++ b/lib/portfoliolib.php
@@ -84,6 +84,7 @@ class portfolio_add_button {
     private $formats;
     private $instances;
     private $file; // for single-file exports
+    private $intendedmimetype; // for writing specific types of files
 
     /**
     * constructor. either pass the options here or set them using the helper methods.
@@ -163,7 +164,8 @@ class portfolio_add_button {
     * @param array $formats if the calling code knows better than the static method on the calling class (base_supported_formats)
     *                       eg, if it's going to be a single file, or if you know it's HTML, you can pass it here instead
     *                       this is almost always the case so you should always use this.
-    *                       {@see portfolio_format_from_file} for how to get the appropriate formats to pass here for uploaded files.
+    *                       {@see portfolio_format_from_mimetype} for how to get the appropriate formats to pass here for uploaded files.
+    *                       or just call set_format_by_file instead
     */
     public function set_formats($formats=null) {
         if (is_string($formats)) {
@@ -179,6 +181,10 @@ class portfolio_add_button {
         $this->formats = portfolio_most_specific_formats($formats, $callerformats);
     }
 
+    /**
+     * reset formats to the default
+     * which is usually what base_supported_formats returns
+     */
     public function reset_formats() {
         $this->set_formats();
     }
@@ -195,13 +201,33 @@ class portfolio_add_button {
      */
     public function set_format_by_file(stored_file $file, $extraformats=null) {
         $this->file = $file;
+        $fileformat = portfolio_format_from_mimetype($file->get_mimetype());
         if (is_string($extraformats)) {
-            $this->set_formats(array(portfolio_format_from_file($file), $extraformats));
-        } else if (is_array($extraformats)) {
-            $this->set_formats(array_merge(array(portfolio_format_from_file($file)), $extraformats));
-        } else  {
-            $this->set_formats(portfolio_format_from_file($file));
+            $extraformats = array($extraformats);
+        } else if (!is_array($extraformats)) {
+            $extraformats = array();
         }
+        $this->set_formats(array_merge(array($fileformat), $extraformats));
+    }
+
+    /**
+     * correllary to set_format_by_file, but this is used when we don't yet have a stored_file
+     * when we're writing out a new type of file (like csv or pdf)
+     *
+     * @param string $extn the file extension we intend to generate
+     * @param mixed        $extraformats any additional formats other than by mimetype
+     *                                  eg leap2a etc
+     */
+    public function set_format_by_intended_file($extn, $extraformats=null) {
+        $mimetype = mimeinfo('type', 'something. ' . $extn);
+        $fileformat = portfolio_format_from_mimetype($mimetype);
+        $this->intendedmimetype = $fileformat;
+        if (is_string($extraformats)) {
+            $extraformats = array($extraformats);
+        } else if (!is_array($extraformats)) {
+            $extraformats = array();
+        }
+        $this->set_formats(array_merge(array($fileformat), $extraformats));
     }
 
     /*
@@ -238,8 +264,7 @@ class portfolio_add_button {
             // use the caller defaults
             $this->set_formats();
         }
-        $formoutput = '<form method="post" action="' . $CFG->wwwroot . '/portfolio/add.php" id="portfolio-add-button">' . "\n";
-        $linkoutput = '<a href="' . $CFG->wwwroot . '/portfolio/add.php?';
+        $url = new moodle_url('/portfolio/add.php');
         foreach ($this->callbackargs as $key => $value) {
             if (!empty($value) && !is_string($value) && !is_numeric($value)) {
                 $a->key = $key;
@@ -247,18 +272,19 @@ class portfolio_add_button {
                 debugging(get_string('nonprimative', 'portfolio', $a));
                 return;
             }
-            $linkoutput .= 'ca_' . $key . '=' . $value . '&amp;';
-            $formoutput .= "\n" . '<input type="hidden" name="ca_' . $key . '" value="' . $value . '" />';
+            $url->param('ca_' . $key, $value);
+        }
+        $url->param('sesskey', sesskey());
+        $url->param('callbackfile', $this->callbackfile);
+        $url->param('callbackclass', $this->callbackclass);
+        $url->param('course', (!empty($COURSE)) ? $COURSE->id : 0);
+        $url->param('callerformats', implode(',', $this->formats));
+        $mimetype = null;
+        if ($this->file instanceof stored_file) {
+            $mimetype = $this->file->get_mimetype();
+        } else if ($this->intendedmimetype) {
+            $mimetype = $this->intendedmimetype;
         }
-        $formoutput .= "\n" . '<input type="hidden" name="sesskey" value="' . sesskey() . '" />';
-        $linkoutput .= 'sesskey=' . sesskey() . '&amp;';
-        $formoutput .= "\n" . '<input type="hidden" name="callbackfile" value="' . $this->callbackfile . '" />';
-        $formoutput .= "\n" . '<input type="hidden" name="callbackclass" value="' . $this->callbackclass . '" />';
-        $formoutput .= "\n" . '<input type="hidden" name="course" value="' . (!empty($COURSE) ? $COURSE->id : 0) . '" />';
-        $formoutput .= "\n" . '<input type="hidden" name="callerformats" value="' . implode(',', $this->formats) . '" />';
-        $linkoutput .= 'callbackfile=' . $this->callbackfile . '&amp;callbackclass='
-            . $this->callbackclass . '&amp;course=' . (!empty($COURSE) ? $COURSE->id : 0)
-            . '&amp;callerformats=' . implode(',', $this->formats);
         $selectoutput = '';
         if (count($this->instances) == 1) {
             $tmp = array_values($this->instances);
@@ -279,19 +305,22 @@ class portfolio_add_button {
                 debugging(get_string('singleinstancenomultiallowed', 'portfolio'));
                 return;
             }
-            if ($this->file && $this->file instanceof stored_file && !$instance->file_mime_check($this->file->get_mimetype())) {
-                // bail, we have a specific file and this plugin doesn't support it
-                debugging(get_string('mimecheckfail', 'portfolio', (object)array('plugin' => $instance->get('plugin'), 'mimetype' => $this->file->get_mimetype())));
+            if ($mimetype&& !$instance->file_mime_check($mimetype)) {
+                // bail, we have a specific file or mimetype and this plugin doesn't support it
+                debugging(get_string('mimecheckfail', 'portfolio', (object)array('plugin' => $instance->get('plugin'), 'mimetype' => $mimetype)));
                 return;
             }
-            $formoutput .= "\n" . '<input type="hidden" name="instance" value="' . $instance->get('id') . '" />';
-            $linkoutput .= '&amp;instance=' . $instance->get('id');
+            $url->param('instance', $instance->get('id'));
         }
         else {
-            if (!$selectoutput = portfolio_instance_select($this->instances, $this->formats, $this->file, $this->callbackclass, 'instance', true)) {
+            if (!$selectoutput = portfolio_instance_select($this->instances, $this->formats, $this->callbackclass, $mimetype, 'instance', true)) {
                 return;
             }
         }
+        // if we just want a url to redirect to, do it now
+        if ($format == PORTFOLIO_ADD_FAKE_URL) {
+            return $url->out(false, array(), false);
+        }
 
         if (empty($addstr)) {
             $addstr = get_string('addtoportfolio', 'portfolio');
@@ -299,6 +328,11 @@ class portfolio_add_button {
         if (empty($format)) {
             $format = PORTFOLIO_ADD_FULL_FORM;
         }
+
+        $formoutput = '<form method="post" action="' . $CFG->wwwroot . '/portfolio/add.php" id="portfolio-add-button">' . "\n";
+        $formoutput .= $url->hidden_params_out();
+        $linkoutput = '<a href="' . $url->out();
+
         switch ($format) {
             case PORTFOLIO_ADD_FULL_FORM:
                 $formoutput .= $selectoutput;
@@ -316,6 +350,8 @@ class portfolio_add_button {
             case PORTFOLIO_ADD_TEXT_LINK:
                 $linkoutput .= '">' . $addstr .'</a>';
             break;
+            case PORTFOLIO_ADD_FAKE_URL:
+                return urldecode($linkoutput);
             default:
                 debugging(get_string('invalidaddformat', 'portfolio', $format));
         }
@@ -383,14 +419,14 @@ class portfolio_add_button {
 * @param array          $instances      array of portfolio plugin instance objects - the instances to put in the menu
 * @param array          $callerformats  array of PORTFOLIO_FORMAT_XXX constants - the formats the caller supports (this is used to filter plugins)
 * @param array          $callbackclass  the callback class name - used for debugging only for when there are no common formats
-* @param stored_file    $file           if we already know we have exactly one file, pass it here to do mime filtering.
+* @param mimetype       $mimetype       if we already know we have exactly one file, or are going to write one, pass it here to do mime filtering.
 * @param string         $selectname     the name of the select element. Optional, defaults to instance.
 * @param boolean        $return         whether to print or return the output. Optional, defaults to print.
 * @param booealn        $returnarray    if returning, whether to return the HTML or the array of options. Optional, defaults to HTML.
 *
 * @return string the html, from <select> to </select> inclusive.
 */
-function portfolio_instance_select($instances, $callerformats, $callbackclass, $file=null, $selectname='instance', $return=false, $returnarray=false) {
+function portfolio_instance_select($instances, $callerformats, $callbackclass, $mimetype=null, $selectname='instance', $return=false, $returnarray=false) {
     global $CFG, $USER;
 
     if (empty($CFG->enableportfolios)) {
@@ -422,8 +458,8 @@ function portfolio_instance_select($instances, $callerformats, $callbackclass, $
             // bail, already exporting something with this plugin and it doesn't support multiple exports
             continue;
         }
-        if ($file && $file instanceof stored_file && !$instance->file_mime_check($file->get_mimetype())) {
-            debugging(get_string('mimecheckfail', 'portfolio', (object)array('plugin' => $instance->get('plugin'), 'mimetype' => $this->file->get_mimetype())));
+        if ($mimetype && !$instance->file_mime_check($mimetype)) {
+            debugging(get_string('mimecheckfail', 'portfolio', (object)array('plugin' => $instance->get('plugin'), 'mimetype' => $mimetype())));
             // bail, we have a specific file and this plugin doesn't support it
             continue;
         }
@@ -516,23 +552,20 @@ function portfolio_supported_formats() {
 * This function returns the revelant portfolio export format
 * which is used to determine which portfolio plugins can be used
 * for exporting this content
-* according to the mime type of the given file
-* this only works when exporting exactly <b>one</b> file
+* according to the given mime type
+* this only works when exporting exactly <b>one</b> file, or generating a new one
+* (like a pdf or csv export)
 *
-* @param stored_file $file file to check mime type for
+* @param string $mimetype (usually $file->get_mimetype())
 *
 * @return string the format constant (see PORTFOLIO_FORMAT_XXX constants)
 */
-function portfolio_format_from_file(stored_file $file) {
+function portfolio_format_from_mimetype($mimetype) {
     global $CFG;
     static $alreadymatched;
     if (empty($alreadymatched)) {
         $alreadymatched = array();
     }
-    if (!($file instanceof stored_file)) {
-        throw new portfolio_exception('invalidfileargument', 'portfolio');
-    }
-    $mimetype = $file->get_mimetype();
     if (array_key_exists($mimetype, $alreadymatched)) {
         return $alreadymatched[$mimetype];
     }
@@ -862,33 +895,6 @@ function portfolio_report_insane($insane, $instances=false, $return=false) {
     echo $output;
 }
 
-/**
-* fake the url to portfolio/add.php from data from somewhere else
-* you should use portfolio_add_button instead 99% of the time
-*
-* @param int    $instanceid   instanceid (optional, will force a new screen if not specified)
-* @param string $classname    callback classname
-* @param string $classfile    file containing the callback class definition
-* @param array  $callbackargs arguments to pass to the callback class
-*/
-function portfolio_fake_add_url($instanceid, $classname, $classfile, $callbackargs, $formats) {
-    global $CFG;
-    $url = $CFG->wwwroot . '/portfolio/add.php?instance=' . $instanceid . '&callbackclass=' . $classname . '&callbackfile=' . $classfile . '&sesskey=' . sesskey();
-
-    if (is_object($callbackargs)) {
-        $callbackargs = (array)$callbackargs;
-    }
-    if (!is_array($callbackargs) || empty($callbackargs)) {
-        return $url;
-    }
-    foreach ($callbackargs as $key => $value) {
-        $url .= '&ca_' . $key . '=' . urlencode($value);
-    }
-    $url .= '&callerformats=' . implode(',', $formats);
-    return $url;
-}
-
-
 
 /**
 * event handler for the portfolio_send event
diff --git a/mod/data/export.php b/mod/data/export.php
index bf2b588db16..85a5eddae10 100644
--- a/mod/data/export.php
+++ b/mod/data/export.php
@@ -98,11 +98,16 @@ if($mform->is_cancelled()) {
 }
 
 if (array_key_exists('portfolio', $formdata) && !empty($formdata['portfolio'])) {
-    // fake  portfolio callback stuff and redirect
+    // fake portfolio callback stuff and redirect
     $formdata['id'] = $cm->id;
-    $formdata['exporttype'] = 'csv'; // force for now
     require_once($CFG->libdir . '/portfoliolib.php');
-    $url = portfolio_fake_add_url($formdata['portfolio'], 'data_portfolio_caller', '/mod/data/locallib.php', $formdata, array(PORTFOLIO_FORMAT_SPREADSHEET));
+    $button = new portfolio_add_button();
+    $button->set_callback_options('data_portfolio_caller', $formdata, '/mod/data/locallib.php');
+    if ($formdata['exporttype'] == 'csv') {
+        $button->set_format_by_intended_file('csv'); // so we can do mime checking
+    }
+    $url = $button->to_html(PORTFOLIO_ADD_FAKE_URL);
+    $url .= '&instance=' . $formdata['portfolio']; // add on the instance since we know it
     redirect($url);
 }
 
diff --git a/mod/data/export_form.php b/mod/data/export_form.php
index 22799c19c12..9c27e418918 100644
--- a/mod/data/export_form.php
+++ b/mod/data/export_form.php
@@ -58,17 +58,21 @@ class mod_data_export_form extends moodleform {
             }
         }
         $this->add_checkbox_controller(1, null, null, 1);
-        require_once($CFG->libdir . '/portfoliolib.php');
-        require_once($CFG->dirroot . '/mod/data/locallib.php');
         if ($CFG->enableportfolios && has_capability('mod/data:exportallentries', get_context_instance(CONTEXT_MODULE, $this->_cm->id))) {
+            require_once($CFG->libdir . '/portfoliolib.php');
+            require_once($CFG->dirroot . '/mod/data/locallib.php');
             if ($portfoliooptions = portfolio_instance_select(
                 portfolio_instances(),
                 call_user_func(array('data_portfolio_caller', 'base_supported_formats')),
-                'data_portfolio_caller', '/mod/data/locallib.php', '', true, true)) {
-                $mform->addElement('header', 'notice', get_string('portfolionotfile', 'data') . ':');
-                $portfoliooptions[0] = get_string('none');
-                ksort($portfoliooptions);
-                $mform->addElement('select', 'portfolio', get_string('portfolio', 'portfolio'), $portfoliooptions);
+                'data_portfolio_caller',
+                mimeinfo('type', 'export.csv'),
+                'instance',
+                true,
+                true)) {
+                    $mform->addElement('header', 'notice', get_string('portfolionotfile', 'data') . ':');
+                    $portfoliooptions[0] = get_string('none');
+                    ksort($portfoliooptions);
+                    $mform->addElement('select', 'portfolio', get_string('portfolio', 'portfolio'), $portfoliooptions);
             }
         }
         $this->add_action_buttons(true, get_string('exportdatabaserecords', 'data'));
diff --git a/mod/data/locallib.php b/mod/data/locallib.php
index 39f82a89fa9..b8c34c1efee 100644
--- a/mod/data/locallib.php
+++ b/mod/data/locallib.php
@@ -301,7 +301,7 @@ class data_portfolio_caller extends portfolio_module_caller_base {
             }
         }
         if (count($includedfiles) == 1 && count($fields) == 1) {
-            $formats= array(portfolio_format_from_file($includedfiles[0]));
+            $formats= array(portfolio_format_from_mimetype($includedfiles[0]->get_mimetype()));
         } else if (count($includedfiles) > 0) {
             $formats = array(PORTFOLIO_FORMAT_RICHHTML);
         }
@@ -309,6 +309,6 @@ class data_portfolio_caller extends portfolio_module_caller_base {
     }
 
     public static function base_supported_formats() {
-        return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML);
+        return array(PORTFOLIO_FORMAT_SPREADSHEET, PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML);
     }
 }
diff --git a/portfolio/add.php b/portfolio/add.php
index 0b988838f95..e9a7f1b080e 100644
--- a/portfolio/add.php
+++ b/portfolio/add.php
@@ -211,7 +211,7 @@ if (!$exporter->get('instance')) {
         portfolio_instances(),
         $exporter->get('caller')->supported_formats(),
         get_class($exporter->get('caller')),
-        $exporter->get('caller')->get('singlefile'),
+        $exporter->get('caller')->get_mimetype(),
         'instance',
         true,
         true