MDL-33791 Portfolio: Fixed security issue with passing file paths.
authorMark Nelson <markn@moodle.com>
Wed, 7 Nov 2012 06:49:52 +0000 (14:49 +0800)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 7 Nov 2012 23:26:13 +0000 (00:26 +0100)
22 files changed:
lang/en/portfolio.php
lib/db/install.xml [changed mode: 0644->0755]
lib/db/upgrade.php
lib/portfolio/exporter.php
lib/portfoliolib.php
mod/assign/locallib.php
mod/assign/portfolio_callback.php [deleted file]
mod/assign/renderable.php
mod/assignment/lib.php
mod/assignment/renderer.php
mod/assignment/type/online/assignment.class.php
mod/chat/report.php
mod/data/lib.php
mod/data/view.php
mod/forum/discuss.php
mod/forum/lib.php
mod/glossary/export.php
mod/glossary/lib.php
portfolio/add.php
portfolio/upgrade.txt
user/portfoliologs.php
version.php

index 8bdb88e..d0bf89a 100644 (file)
@@ -145,7 +145,8 @@ $string['multipleinstancesdisallowed'] = 'Trying to create another instance of a
 $string['mustsetcallbackoptions'] = 'You must set the callback options either in the portfolio_add_button constructor or using the set_callback_options method';
 $string['noavailableplugins'] = 'Sorry, but there are no available portfolios for you to export to';
 $string['nocallbackclass'] = 'Could not find the callback class to use ({$a})';
-$string['nocallbackfile'] = 'Something in the module you\'re trying to export from is broken - couldn\'t find a required file ({$a})';
+$string['nocallbackcomponent'] = 'Could not find the component specified {$a}.';
+$string['nocallbackfile'] = 'Something in the module you\'re trying to export from is broken - couldn\'t find a required portfolio file';
 $string['noclassbeforeformats'] = 'You must set the callback class before calling set_formats in portfolio_button';
 $string['nocommonformats'] = 'No common formats between any available portfolio plugin and the calling location {$a->location} (caller supported {$a->formats})';
 $string['noinstanceyet'] = 'Not yet selected';
old mode 100644 (file)
new mode 100755 (executable)
index 96dc090..aee878f
         <FIELD NAME="time" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="time of transfer (in the case of a queued transfer this is the time the actual transfer ran, not when the user started)" PREVIOUS="userid" NEXT="portfolio"/>
         <FIELD NAME="portfolio" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="fk to portfolio_instance" PREVIOUS="time" NEXT="caller_class"/>
         <FIELD NAME="caller_class" TYPE="char" LENGTH="150" NOTNULL="true" SEQUENCE="false" COMMENT="the name of the class used to create the transfer" PREVIOUS="portfolio" NEXT="caller_file"/>
-        <FIELD NAME="caller_file" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="path to file to include where the class definition lives. (relative to dirroot)" PREVIOUS="caller_class" NEXT="caller_sha1"/>
-        <FIELD NAME="caller_sha1" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="sha1 of exported content as far as the caller is concerned (before the portfolio plugin gets a hold of it)" PREVIOUS="caller_file" NEXT="tempdataid"/>
+        <FIELD NAME="caller_file" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="path to file to include where the class definition lives. (relative to dirroot)" PREVIOUS="caller_class" NEXT="caller_component"/>
+        <FIELD NAME="caller_component" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="the component name responsible for exporting" PREVIOUS="caller_file" NEXT="caller_sha1"/>
+        <FIELD NAME="caller_sha1" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="sha1 of exported content as far as the caller is concerned (before the portfolio plugin gets a hold of it)" PREVIOUS="caller_component" NEXT="tempdataid"/>
         <FIELD NAME="tempdataid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="old id from portfolio_tempdata.  This is so that we can gracefully catch a race condition between an external system requesting a file and causing the tempdata to be deleted, before the user gets the &quot;your transfer is requested&quot; page" PREVIOUS="caller_sha1" NEXT="returnurl"/>
         <FIELD NAME="returnurl" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="the original &quot;returnurl&quot; of the export - takes us to the moodle page we started from" PREVIOUS="tempdataid" NEXT="continueurl"/>
         <FIELD NAME="continueurl" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="the url the external system has set to view the transfer" PREVIOUS="returnurl"/>
index 8ade318..6e904b2 100644 (file)
@@ -1399,5 +1399,20 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2012110201.00);
     }
 
+    if ($oldversion < 2012110700.01) {
+
+        // Define field caller_component to be added to portfolio_log.
+        $table = new xmldb_table('portfolio_log');
+        $field = new xmldb_field('caller_component', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'caller_file');
+
+        // Conditionally launch add field caller_component.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2012110700.01);
+    }
+
     return true;
 }
index 79bfc8f..3225ddf 100644 (file)
@@ -66,10 +66,10 @@ class portfolio_exporter {
     public $instancefile;
 
     /**
-     * @var string the file to include that contains the class definition of
+     * @var string the component that contains the class definition of
      *             the caller object used to re-waken the object after sleep
      */
-    public $callerfile;
+    public $callercomponent;
 
     /** @var int the current stage of the export */
     private $stage;
@@ -115,16 +115,16 @@ class portfolio_exporter {
      *
      * @param portfolio_plugin_base $instance portfolio instance (passed by reference)
      * @param portfolio_caller_base $caller portfolio caller (passed by reference)
-     * @param string $callerfile path to callerfile (relative to dataroot)
+     * @param string $callercomponent the name of the callercomponent
      */
-    public function __construct(&$instance, &$caller, $callerfile) {
+    public function __construct(&$instance, &$caller, $callercomponent) {
         $this->instance =& $instance;
         $this->caller =& $caller;
         if ($instance) {
             $this->instancefile = 'portfolio/' . $instance->get('plugin') . '/lib.php';
             $this->instance->set('exporter', $this);
         }
-        $this->callerfile = $callerfile;
+        $this->callercomponent = $callercomponent;
         $this->stage = PORTFOLIO_STAGE_CONFIG;
         $this->caller->set('exporter', $this);
         $this->alreadystolen = array();
@@ -395,7 +395,13 @@ class portfolio_exporter {
             foreach ($previous as $row) {
                 $previousstr .= userdate($row->time);
                 if ($row->caller_class != get_class($this->caller)) {
-                    require_once($CFG->dirroot . '/' . $row->caller_file);
+                    if (!empty($row->caller_file)) {
+                        portfolio_include_callback_file($row->caller_file);
+                    } else if (!empty($row->caller_component)) {
+                        portfolio_include_callback_file($row->caller_component);
+                    } else { // Ok, that's weird - this should never happen. Is the apocalypse coming?
+                        continue;
+                    }
                     $previousstr .= ' (' . call_user_func(array($row->caller_class, 'display_name')) . ')';
                 }
                 $previousstr .= '<br />';
@@ -522,15 +528,16 @@ class portfolio_exporter {
     public function log_transfer() {
         global $DB;
         $l = array(
-            'userid'         => $this->user->id,
-            'portfolio'      => $this->instance->get('id'),
-            'caller_file'    => $this->callerfile,
-            'caller_sha1'    => $this->caller->get_sha1(),
-            'caller_class'   => get_class($this->caller),
-            'continueurl'    => $this->instance->get_static_continue_url(),
-            'returnurl'      => $this->caller->get_return_url(),
-            'tempdataid'     => $this->id,
-            'time'           => time(),
+            'userid' => $this->user->id,
+            'portfolio' => $this->instance->get('id'),
+            'caller_file'=> '',
+            'caller_component' => $this->callercomponent,
+            'caller_sha1' => $this->caller->get_sha1(),
+            'caller_class' => get_class($this->caller),
+            'continueurl' => $this->instance->get_static_continue_url(),
+            'returnurl' => $this->caller->get_return_url(),
+            'tempdataid' => $this->id,
+            'time' => time(),
         );
         $DB->insert_record('portfolio_log', $l);
     }
@@ -681,7 +688,14 @@ class portfolio_exporter {
         if ($exporter->instancefile) {
             require_once($CFG->dirroot . '/' . $exporter->instancefile);
         }
-        require_once($CFG->dirroot . '/' . $exporter->callerfile);
+        if (!empty($exporter->callerfile)) {
+            portfolio_include_callback_file($exporter->callerfile);
+        } else if (!empty($exporter->callercomponent)) {
+            portfolio_include_callback_file($exporter->callercomponent);
+        } else {
+            return; // Should never get here!
+        }
+
         $exporter = unserialize(serialize($exporter));
         if (!$exporter->get('id')) {
             // workaround for weird case
index 04a2223..37d91dc 100644 (file)
@@ -60,12 +60,12 @@ require_once($CFG->libdir . '/portfolio/caller.php');
  * This class can be used like this:
  * <code>
  * $button = new portfolio_add_button();
- * $button->set_callback_options('name_of_caller_class', array('id' => 6), '/your/mod/lib.php');
- * $button->render(PORTFOLIO_ADD_FULL_FORM, get_string('addeverythingtoportfolio', 'yourmodule'));
+ * $button->set_callback_options('name_of_caller_class', array('id' => 6), 'yourcomponent'); eg. mod_forum
+ * $button->render(PORTFOLIO_ADD_FULL_FORM, get_string('addeverythingtoportfolio', 'yourcomponent'));
  * </code>
  * or like this:
  * <code>
- * $button = new portfolio_add_button(array('callbackclass' => 'name_of_caller_class', 'callbackargs' => array('id' => 6), 'callbackfile' => '/your/mod/lib.php'));
+ * $button = new portfolio_add_button(array('callbackclass' => 'name_of_caller_class', 'callbackargs' => array('id' => 6), 'callbackcomponent' => 'yourcomponent')); eg. mod_forum
  * $somehtml .= $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
  * </code>
  *{@link http://docs.moodle.org/dev/Adding_a_Portfolio_Button_to_a_page} for more information
@@ -84,7 +84,7 @@ class portfolio_add_button {
     private $callbackargs;
 
     /** @var string caller file */
-    private $callbackfile;
+    private $callbackcomponent;
 
     /** @var array array of more specific formats (eg based on mime detection) */
     private $formats;
@@ -105,7 +105,7 @@ class portfolio_add_button {
      * @param array $options keyed array of options:
      *                       key 'callbackclass': name of the caller class (eg forum_portfolio_caller')
      *                       key 'callbackargs': the array of callback arguments your caller class wants passed to it in the constructor
-     *                       key 'callbackfile': the file containing the class definition of your caller class.
+     *                       key 'callbackcomponent': the file containing the class definition of your caller class.
      *                       See set_callback_options for more information on these three.
      *                       key 'formats': an array of PORTFOLIO_FORMATS this caller will support
      *                       See set_formats or set_format_by_file for more information on this.
@@ -121,54 +121,42 @@ class portfolio_add_button {
         if (empty($options)) {
             return true;
         }
-        $constructoroptions = array('callbackclass', 'callbackargs', 'callbackfile', 'formats');
+        $constructoroptions = array('callbackclass', 'callbackargs', 'callbackcomponent');
         foreach ((array)$options as $key => $value) {
             if (!in_array($key, $constructoroptions)) {
                 throw new portfolio_button_exception('invalidbuttonproperty', 'portfolio', $key);
             }
-            $this->{$key} = $value;
         }
+
+        $this->set_callback_options($options['callbackclass'], $options['callbackargs'], $options['callbackcomponent']);
     }
 
     /**
      * Function to set the callback options
      *
-     * @param string $class   Name of the class containing the callback functions
-     *                        activity modules should ALWAYS use their name_portfolio_caller
-     *                        other locations must use something unique
+     * @param string $class Name of the class containing the callback functions
+     *      activity components should ALWAYS use their name_portfolio_caller
+     *      other locations must use something unique
      * @param array $argarray This can be an array or hash of arguments to pass
-     *                        back to the callback functions (passed by reference)
-     *                        these MUST be primatives to be added as hidden form fields.
-     *                        and the values get cleaned to PARAM_ALPHAEXT or PARAM_FLOAT or PARAM_PATH
-     * @param string $file    This can be autodetected if it's in the same file as your caller,
-     *                        but often, the caller is a script.php and the class in a lib.php
-     *                        so you can pass it here if necessary.
-     *                        This path should be relative (ie, not include) dirroot, eg '/mod/forum/lib.php'
+     *      back to the callback functions (passed by reference)
+     *      these MUST be primatives to be added as hidden form fields.
+     *      and the values get cleaned to PARAM_ALPHAEXT or PARAM_FLOAT or PARAM_PATH
+     * @param string $component This is the name of the component in Moodle, eg 'mod_forum'
      */
-    public function set_callback_options($class, array $argarray, $file=null) {
+    public function set_callback_options($class, array $argarray, $component) {
         global $CFG;
-        if (empty($file)) {
-            $backtrace = debug_backtrace();
-            if (!array_key_exists(0, $backtrace) || !array_key_exists('file', $backtrace[0]) || !is_readable($backtrace[0]['file'])) {
-                throw new portfolio_button_exception('nocallbackfile', 'portfolio');
-            }
 
-            $file = substr($backtrace[0]['file'], strlen($CFG->dirroot));
-        } else if (!is_readable($CFG->dirroot . $file)) {
-            throw new portfolio_button_exception('nocallbackfile', 'portfolio', '', $file);
-        }
-        $this->callbackfile = $file;
-        require_once($CFG->libdir . '/portfolio/caller.php'); // require the base class first
-        require_once($CFG->dirroot . $file);
-        if (!class_exists($class)) {
-            throw new portfolio_button_exception('nocallbackclass', 'portfolio', '', $class);
-        }
+        // Require the base class first before any other files.
+        require_once($CFG->libdir . '/portfolio/caller.php');
 
-        // this will throw exceptions
-        // but should not actually do anything other than verify callbackargs
+        // Include any potential callback files and check for errors.
+        portfolio_include_callback_file($component, $class);
+
+        // This will throw exceptions but should not actually do anything other than verify callbackargs.
         $test = new $class($argarray);
         unset($test);
 
+        $this->callbackcomponent = $component;
         $this->callbackclass = $class;
         $this->callbackargs = $argarray;
     }
@@ -278,7 +266,7 @@ class portfolio_add_button {
         if (!$this->is_renderable()) {
             return;
         }
-        if (empty($this->callbackclass) || empty($this->callbackfile)) {
+        if (empty($this->callbackclass) || empty($this->callbackcomponent)) {
             throw new portfolio_button_exception('mustsetcallbackoptions', 'portfolio');
         }
         if (empty($this->formats)) {
@@ -297,7 +285,7 @@ class portfolio_add_button {
             $url->param('ca_' . $key, $value);
         }
         $url->param('sesskey', sesskey());
-        $url->param('callbackfile', $this->callbackfile);
+        $url->param('callbackcomponent', $this->callbackcomponent);
         $url->param('callbackclass', $this->callbackclass);
         $url->param('course', (!empty($COURSE)) ? $COURSE->id : 0);
         $url->param('callerformats', implode(',', $this->formats));
@@ -421,12 +409,12 @@ class portfolio_add_button {
     }
 
     /**
-     * Getter for $callbackfile property
+     * Getter for $callbackcomponent property
      *
      * @return string
      */
-    public function get_callbackfile() {
-        return $this->callbackfile;
+    public function get_callbackcomponent() {
+        return $this->callbackcomponent;
     }
 
     /**
@@ -1271,6 +1259,89 @@ function portfolio_rewrite_pluginfile_url_callback($contextid, $component, $file
     return $format->file_output($file, $options);
 }
 
+/**
+ * Function to require any potential callback files, throwing exceptions
+ * if an issue occurs.
+ *
+ * @param string $component This is the name of the component in Moodle, eg 'mod_forum'
+ * @param string $class Name of the class containing the callback functions
+ *     activity components should ALWAYS use their name_portfolio_caller
+ *     other locations must use something unique
+ */
+function portfolio_include_callback_file($component, $class = null) {
+    global $CFG;
+    require_once($CFG->libdir . '/adminlib.php');
+
+    // It's possible that they are passing a file path rather than passing a component.
+    // We want to try and convert this to a component name, eg. mod_forum.
+    $pos = strrpos($component, '/');
+    if ($pos !== false) {
+        // Get rid of the first slash (if it exists).
+        $component = ltrim($component, '/');
+        // Get a list of valid plugin types.
+        $plugintypes = get_plugin_types(false);
+        // Assume it is not valid for now.
+        $isvalid = false;
+        // Go through the plugin types.
+        foreach ($plugintypes as $type => $path) {
+            if (strrpos($component, $path) === 0) {
+                // Found the plugin type.
+                $isvalid = true;
+                $plugintype = $type;
+                $pluginpath = $path;
+            }
+        }
+        // Throw exception if not a valid component.
+        if (!$isvalid) {
+            throw new coding_exception('Somehow a non-valid plugin path was passed, could be a hackz0r attempt, exiting.');
+        }
+        // Remove the file name.
+        $component = trim(substr($component, 0, $pos), '/');
+        // Replace the path with the type.
+        $component = str_replace($pluginpath, $plugintype, $component);
+        // Ok, replace '/' with '_'.
+        $component = str_replace('/', '_', $component);
+        // Place a debug message saying the third parameter should be changed.
+        debugging('The third parameter sent to the function set_callback_options should be the component name, not a file path, please update this.', DEBUG_DEVELOPER);
+    }
+
+    // Check that it is a valid component.
+    if (!get_component_version($component)) {
+        throw new portfolio_button_exception('nocallbackcomponent', 'portfolio', '', $component);
+    }
+
+    // Obtain the component's location.
+    if (!$componentloc = get_component_directory($component)) {
+        throw new portfolio_button_exception('nocallbackcomponent', 'portfolio', '', $component);
+    }
+
+    // Check if the component contains the necessary file for the portfolio plugin.
+    // These are locallib.php, portfoliolib.php and portfolio_callback.php.
+    $filefound = false;
+    if (file_exists($componentloc . '/locallib.php')) {
+        $filefound = true;
+        require_once($componentloc . '/locallib.php');
+    }
+    if (file_exists($componentloc . '/portfoliolib.php')) {
+        $filefound = true;
+        debugging('Please standardise your plugin by renaming your portfolio callback file to locallib.php, or if that file already exists moving the portfolio functionality there.', DEBUG_DEVELOPER);
+        require_once($componentloc . '/portfoliolib.php');
+    }
+    if (file_exists($componentloc . '/portfolio_callback.php')) {
+        $filefound = true;
+        debugging('Please standardise your plugin by renaming your portfolio callback file to locallib.php, or if that file already exists moving the portfolio functionality there.', DEBUG_DEVELOPER);
+        require_once($componentloc . '/portfolio_callback.php');
+    }
+
+    // Ensure that we found a file we can use, if not throw an exception.
+    if (!$filefound) {
+        throw new portfolio_button_exception('nocallbackfile', 'portfolio', '', $component);
+    }
+
+    if (!is_null($class) && !class_exists($class)) {
+        throw new portfolio_button_exception('nocallbackclass', 'portfolio', '', $class);
+    }
+}
 
 /**
  * Go through all the @@PLUGINFILE@@ matches in some text,
index a4ab939..ddc9d01 100644 (file)
@@ -61,7 +61,8 @@ require_once($CFG->dirroot.'/mod/assign/renderable.php');
 require_once($CFG->dirroot.'/mod/assign/gradingtable.php');
 /** Include eventslib.php */
 require_once($CFG->libdir.'/eventslib.php');
-
+/** Include portfolio caller.php */
+require_once($CFG->libdir . '/portfolio/caller.php');
 
 /**
  * Standard base class for mod_assign (assignment types).
@@ -1759,7 +1760,9 @@ class assign {
             require_once($CFG->libdir . '/portfoliolib.php');
 
             $button = new portfolio_add_button();
-            $button->set_callback_options('assign_portfolio_caller', array('cmid' => $this->get_course_module()->id, 'sid' => $submissionid, 'plugin' => $plugintype, 'editor' => $editor, 'area'=>$filearea), '/mod/assign/portfolio_callback.php');
+            $button->set_callback_options('assign_portfolio_caller', array('cmid' => $this->get_course_module()->id,
+                                          'sid' => $submissionid, 'plugin' => $plugintype, 'editor' => $editor, 'area'=>$filearea),
+                                          'mod_assign');
             $fs = get_file_storage();
 
             if ($files = $fs->get_area_files($this->context->id, $component,$filearea, $submissionid, "timemodified", false)) {
@@ -4545,3 +4548,250 @@ class assign {
 
 }
 
+/**
+ * portfolio caller class for mod_assign.
+ *
+ * @package   mod_assign
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assign_portfolio_caller extends portfolio_module_caller_base {
+
+    /** @var int callback arg - the id of submission we export */
+    protected $sid;
+
+    /** @var string component of the submission files we export*/
+    protected $component;
+
+    /** @var string callback arg - the area of submission files we export */
+    protected $area;
+
+    /** @var int callback arg - the id of file we export */
+    protected $fileid;
+
+    /** @var int callback arg - the cmid of the assignment we export */
+    protected $cmid;
+
+    /** @var string callback arg - the plugintype of the editor we export */
+    protected $plugin;
+
+    /** @var string callback arg - the name of the editor field we export */
+    protected $editor;
+
+   /**
+    * callback arg for a single file export
+    */
+    public static function expected_callbackargs() {
+        return array(
+            'cmid' => true,
+            'sid' => false,
+            'area' => false,
+            'component' => false,
+            'fileid' => false,
+            'plugin' => false,
+            'editor' => false,
+       );
+    }
+
+    /**
+     * the constructor
+     * @param array $callbackargs
+     */
+    function __construct($callbackargs) {
+        parent::__construct($callbackargs);
+        $this->cm = get_coursemodule_from_id('assign', $this->cmid, 0, false, MUST_EXIST);
+    }
+
+    /**
+     *
+     * Load data needed for the portfolio export
+     *
+     * If the assignment type implements portfolio_load_data(), the processing is delegated
+     * to it. Otherwise, the caller must provide either fileid (to export single file) or
+     * submissionid and filearea (to export all data attached to the given submission file area) via callback arguments.
+     *
+     * @throws     portfolio_caller_exception
+     */
+    public function load_data() {
+
+        $context = context_module::instance($this->cmid);
+
+        if (empty($this->fileid)) {
+            if (empty($this->sid) || empty($this->area)) {
+                throw new portfolio_caller_exception('invalidfileandsubmissionid', 'mod_assign');
+            }
+
+        }
+
+        // export either an area of files or a single file (see function for more detail)
+        // the first arg is an id or null. If it is an id, the rest of the args are ignored
+        // if it is null, the rest of the args are used to load a list of files from get_areafiles
+        $this->set_file_and_format_data($this->fileid, $context->id, $this->component, $this->area, $this->sid, 'timemodified', false);
+
+    }
+
+    /**
+     * prepares the package up before control is passed to the portfolio plugin.
+     *
+     * @throws portfolio_caller_exception
+     * @return mixed
+     */
+    public function prepare_package() {
+
+        if ($this->plugin && $this->editor) {
+            $options = portfolio_format_text_options();
+            $context = context_module::instance($this->cmid);
+            $options->context = $context;
+
+            $plugin = $this->get_submission_plugin();
+
+            $text = $plugin->get_editor_text($this->editor, $this->sid);
+            $format = $plugin->get_editor_format($this->editor, $this->sid);
+
+            $html = format_text($text, $format, $options);
+            $html = portfolio_rewrite_pluginfile_urls($html, $context->id, 'mod_assign', $this->area, $this->sid, $this->exporter->get('format'));
+
+            if (in_array($this->exporter->get('formatclass'), array(PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_RICHHTML))) {
+                if ($files = $this->exporter->get('caller')->get('multifiles')) {
+                    foreach ($files as $file) {
+                        $this->exporter->copy_existing_file($file);
+                    }
+                }
+                return $this->exporter->write_new_file($html, 'assignment.html', !empty($files));
+            } else if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
+                $leapwriter = $this->exporter->get('format')->leap2a_writer();
+                $entry = new portfolio_format_leap2a_entry($this->area . $this->cmid, print_context_name($context), 'resource', $html);
+
+                $entry->add_category('web', 'resource_type');
+                $entry->author = $this->user;
+                $leapwriter->add_entry($entry);
+                if ($files = $this->exporter->get('caller')->get('multifiles')) {
+                    $leapwriter->link_files($entry, $files, $this->area . $this->cmid . 'file');
+                    foreach ($files as $file) {
+                        $this->exporter->copy_existing_file($file);
+                    }
+                }
+                return $this->exporter->write_new_file($leapwriter->to_xml(), $this->exporter->get('format')->manifest_name(), true);
+            } else {
+                debugging('invalid format class: ' . $this->exporter->get('formatclass'));
+            }
+
+        }
+
+
+        if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
+            $leapwriter = $this->exporter->get('format')->leap2a_writer();
+            $files = array();
+            if ($this->singlefile) {
+                $files[] = $this->singlefile;
+            } elseif ($this->multifiles) {
+                $files = $this->multifiles;
+            } else {
+                throw new portfolio_caller_exception('invalidpreparepackagefile', 'portfolio', $this->get_return_url());
+            }
+
+            $entryids = array();
+            foreach ($files as $file) {
+                $entry = new portfolio_format_leap2a_file($file->get_filename(), $file);
+                $entry->author = $this->user;
+                $leapwriter->add_entry($entry);
+                $this->exporter->copy_existing_file($file);
+                $entryids[] = $entry->id;
+            }
+            if (count($files) > 1) {
+                $baseid = 'assign' . $this->cmid . $this->area;
+                $context = context_module::instance($this->cmid);
+
+                // if we have multiple files, they should be grouped together into a folder
+                $entry = new portfolio_format_leap2a_entry($baseid . 'group', print_context_name($context), 'selection');
+                $leapwriter->add_entry($entry);
+                $leapwriter->make_selection($entry, $entryids, 'Folder');
+            }
+            return $this->exporter->write_new_file($leapwriter->to_xml(), $this->exporter->get('format')->manifest_name(), true);
+        }
+        return $this->prepare_package_file();
+    }
+
+    /**
+     * fetch the plugin by its type
+     *
+     * @return assign_submission_plugin
+     */
+    private function get_submission_plugin() {
+        global $CFG;
+        if (!$this->plugin || !$this->cmid) {
+            return null;
+        }
+
+        require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+        $context = context_module::instance($this->cmid);
+
+        $assignment = new assign($context, null, null);
+        return $assignment->get_submission_plugin_by_type($this->plugin);
+    }
+
+    /**
+     * get sha1 file
+     * calculate a sha1 has of either a single file or a list
+     * of files based on the data set by load_data
+     *
+     * @return string
+     */
+    public function get_sha1() {
+
+        if ($this->plugin && $this->editor) {
+            $plugin = $this->get_submission_plugin();
+            $options = portfolio_format_text_options();
+            $options->context = context_module::instance($this->cmid);
+
+            $textsha1 = sha1(format_text($plugin->get_editor_text($this->editor, $this->sid),
+                                         $plugin->get_editor_format($this->editor, $this->sid), $options));
+            $filesha1 = '';
+            try {
+                $filesha1 = $this->get_sha1_file();
+            } catch (portfolio_caller_exception $e) {} // no files
+            return sha1($textsha1 . $filesha1);
+        }
+        return $this->get_sha1_file();
+    }
+
+    /**
+     * calculate the time to transfer either a single file or a list
+     * of files based on the data set by load_data
+     *
+     * @return int
+     */
+    public function expected_time() {
+        return $this->expected_time_file();
+    }
+
+    /**
+     * checking the permissions
+     *
+     * @return bool
+     */
+    public function check_permissions() {
+        $context = context_module::instance($this->cmid);
+        return has_capability('mod/assign:exportownsubmission', $context);
+    }
+
+    /**
+     * display a module name
+     *
+     * @return string
+     */
+    public static function display_name() {
+        return get_string('modulename', 'assign');
+    }
+
+    /**
+     * return array of formats supported by this portfolio call back
+     * @return array
+     */
+    public static function base_supported_formats() {
+
+        return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
+
+    }
+}
\ No newline at end of file
diff --git a/mod/assign/portfolio_callback.php b/mod/assign/portfolio_callback.php
deleted file mode 100644 (file)
index 71e6d82..0000000
+++ /dev/null
@@ -1,283 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-//
-// this file contains all the functions that aren't needed by core moodle
-// but start becoming required once we're actually inside the assignment module.
-
-/**
- * This file contains the callback class required by the portfolio api.
- *
- * @package   mod_assign
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/** Include assign locallib.php */
-require_once($CFG->dirroot . '/mod/assign/locallib.php');
-/** Include portfolio caller.php */
-require_once($CFG->libdir . '/portfolio/caller.php');
-
-/**
- * portfolio caller class for mod_assign.
- *
- * @package   mod_assign
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class assign_portfolio_caller extends portfolio_module_caller_base {
-
-
-    /** @var int callback arg - the id of submission we export */
-    protected $sid;
-
-    /** @var string component of the submission files we export*/
-    protected $component;
-
-    /** @var string callback arg - the area of submission files we export */
-    protected $area;
-
-    /** @var int callback arg - the id of file we export */
-    protected $fileid;
-
-    /** @var int callback arg - the cmid of the assignment we export */
-    protected $cmid;
-
-    /** @var string callback arg - the plugintype of the editor we export */
-    protected $plugin;
-
-    /** @var string callback arg - the name of the editor field we export */
-    protected $editor;
-
-   /**
-    * callback arg for a single file export
-    */
-    public static function expected_callbackargs() {
-        return array(
-            'cmid' => true,
-            'sid' => false,
-            'area' => false,
-            'component' => false,
-            'fileid' => false,
-            'plugin' => false,
-            'editor' => false,
-       );
-    }
-
-    /**
-     * the constructor
-     * @param array $callbackargs
-     */
-    function __construct($callbackargs) {
-        parent::__construct($callbackargs);
-        $this->cm = get_coursemodule_from_id('assign', $this->cmid, 0, false, MUST_EXIST);
-    }
-
-    /**
-     *
-     * Load data needed for the portfolio export
-     *
-     * If the assignment type implements portfolio_load_data(), the processing is delegated
-     * to it. Otherwise, the caller must provide either fileid (to export single file) or
-     * submissionid and filearea (to export all data attached to the given submission file area) via callback arguments.
-     *
-     * @throws     portfolio_caller_exception
-     */
-    public function load_data() {
-
-        $context = context_module::instance($this->cmid);
-
-        if (empty($this->fileid)) {
-            if (empty($this->sid) || empty($this->area)) {
-                throw new portfolio_caller_exception('invalidfileandsubmissionid', 'mod_assign');
-            }
-
-        }
-
-        // export either an area of files or a single file (see function for more detail)
-        // the first arg is an id or null. If it is an id, the rest of the args are ignored
-        // if it is null, the rest of the args are used to load a list of files from get_areafiles
-        $this->set_file_and_format_data($this->fileid, $context->id, $this->component, $this->area, $this->sid, 'timemodified', false);
-
-    }
-
-    /**
-     * prepares the package up before control is passed to the portfolio plugin.
-     *
-     * @throws portfolio_caller_exception
-     * @return mixed
-     */
-    public function prepare_package() {
-
-        if ($this->plugin && $this->editor) {
-            $options = portfolio_format_text_options();
-            $context = context_module::instance($this->cmid);
-            $options->context = $context;
-
-            $plugin = $this->get_submission_plugin();
-
-            $text = $plugin->get_editor_text($this->editor, $this->sid);
-            $format = $plugin->get_editor_format($this->editor, $this->sid);
-
-            $html = format_text($text, $format, $options);
-            $html = portfolio_rewrite_pluginfile_urls($html, $context->id, 'mod_assign', $this->area, $this->sid, $this->exporter->get('format'));
-
-            if (in_array($this->exporter->get('formatclass'), array(PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_RICHHTML))) {
-                if ($files = $this->exporter->get('caller')->get('multifiles')) {
-                    foreach ($files as $file) {
-                        $this->exporter->copy_existing_file($file);
-                    }
-                }
-                return $this->exporter->write_new_file($html, 'assignment.html', !empty($files));
-            } else if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
-                $leapwriter = $this->exporter->get('format')->leap2a_writer();
-                $entry = new portfolio_format_leap2a_entry($this->area . $this->cmid, print_context_name($context), 'resource', $html);
-
-                $entry->add_category('web', 'resource_type');
-                $entry->author = $this->user;
-                $leapwriter->add_entry($entry);
-                if ($files = $this->exporter->get('caller')->get('multifiles')) {
-                    $leapwriter->link_files($entry, $files, $this->area . $this->cmid . 'file');
-                    foreach ($files as $file) {
-                        $this->exporter->copy_existing_file($file);
-                    }
-                }
-                return $this->exporter->write_new_file($leapwriter->to_xml(), $this->exporter->get('format')->manifest_name(), true);
-            } else {
-                debugging('invalid format class: ' . $this->exporter->get('formatclass'));
-            }
-
-        }
-
-
-        if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
-            $leapwriter = $this->exporter->get('format')->leap2a_writer();
-            $files = array();
-            if ($this->singlefile) {
-                $files[] = $this->singlefile;
-            } elseif ($this->multifiles) {
-                $files = $this->multifiles;
-            } else {
-                throw new portfolio_caller_exception('invalidpreparepackagefile', 'portfolio', $this->get_return_url());
-            }
-
-            $entryids = array();
-            foreach ($files as $file) {
-                $entry = new portfolio_format_leap2a_file($file->get_filename(), $file);
-                $entry->author = $this->user;
-                $leapwriter->add_entry($entry);
-                $this->exporter->copy_existing_file($file);
-                $entryids[] = $entry->id;
-            }
-            if (count($files) > 1) {
-                $baseid = 'assign' . $this->cmid . $this->area;
-                $context = context_module::instance($this->cmid);
-
-                // if we have multiple files, they should be grouped together into a folder
-                $entry = new portfolio_format_leap2a_entry($baseid . 'group', print_context_name($context), 'selection');
-                $leapwriter->add_entry($entry);
-                $leapwriter->make_selection($entry, $entryids, 'Folder');
-            }
-            return $this->exporter->write_new_file($leapwriter->to_xml(), $this->exporter->get('format')->manifest_name(), true);
-        }
-        return $this->prepare_package_file();
-    }
-
-    /**
-     * fetch the plugin by its type
-     *
-     * @return assign_submission_plugin
-     */
-    private function get_submission_plugin() {
-        global $CFG;
-        if (!$this->plugin || !$this->cmid) {
-            return null;
-        }
-
-        require_once($CFG->dirroot . '/mod/assign/locallib.php');
-
-        $context = context_module::instance($this->cmid);
-
-        $assignment = new assign($context, null, null);
-        return $assignment->get_submission_plugin_by_type($this->plugin);
-    }
-
-    /**
-     * get sha1 file
-     * calculate a sha1 has of either a single file or a list
-     * of files based on the data set by load_data
-     *
-     * @return string
-     */
-    public function get_sha1() {
-
-        if ($this->plugin && $this->editor) {
-            $plugin = $this->get_submission_plugin();
-            $options = portfolio_format_text_options();
-            $options->context = context_module::instance($this->cmid);
-
-            $textsha1 = sha1(format_text($plugin->get_editor_text($this->editor, $this->sid),
-                                         $plugin->get_editor_format($this->editor, $this->sid), $options));
-            $filesha1 = '';
-            try {
-                $filesha1 = $this->get_sha1_file();
-            } catch (portfolio_caller_exception $e) {} // no files
-            return sha1($textsha1 . $filesha1);
-        }
-        return $this->get_sha1_file();
-    }
-
-    /**
-     * calculate the time to transfer either a single file or a list
-     * of files based on the data set by load_data
-     *
-     * @return int
-     */
-    public function expected_time() {
-        return $this->expected_time_file();
-    }
-
-    /**
-     * checking the permissions
-     *
-     * @return bool
-     */
-    public function check_permissions() {
-        $context = context_module::instance($this->cmid);
-        return has_capability('mod/assign:exportownsubmission', $context);
-    }
-
-    /**
-     * display a module name
-     *
-     * @return string
-     */
-    public static function display_name() {
-        return get_string('modulename', 'assign');
-    }
-
-    /**
-     * return array of formats supported by this portfolio call back
-     * @return array
-     */
-    public static function base_supported_formats() {
-
-        return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
-
-    }
-}
-
index 90b16bd..986f1da 100644 (file)
@@ -547,7 +547,12 @@ class assign_files implements renderable {
             require_once($CFG->libdir . '/portfoliolib.php');
             if (count($files) >= 1 && has_capability('mod/assign:exportownsubmission', $this->context)) {
                 $button = new portfolio_add_button();
-                $button->set_callback_options('assign_portfolio_caller', array('cmid' => $this->cm->id, 'sid'=>$sid, 'area'=>$filearea, 'component'=>$component), '/mod/assign/portfolio_callback.php');
+                $button->set_callback_options('assign_portfolio_caller',
+                                              array('cmid' => $this->cm->id,
+                                                    'sid' => $sid,
+                                                    'area' => $filearea,
+                                                    'component' => $component),
+                                              'mod_assign');
                 $button->reset_formats();
                 $this->portfolioform = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
             }
@@ -594,7 +599,9 @@ class assign_files implements renderable {
             if (!empty($CFG->enableportfolios)) {
                 $button = new portfolio_add_button();
                 if (has_capability('mod/assign:exportownsubmission', $this->context)) {
-                    $button->set_callback_options('assign_portfolio_caller', array('cmid' => $this->cm->id, 'fileid' => $file->get_id()), '/mod/assign/portfolio_callback.php');
+                    $button->set_callback_options('assign_portfolio_caller',
+                                                  array('cmid' => $this->cm->id, 'fileid' => $file->get_id()),
+                                                  'mod_assign');
                     $button->set_format_by_file($file);
                     $file->portfoliobutton = $button->to_html(PORTFOLIO_ADD_ICON_LINK);
                 }
index 33ce9bd..3198609 100644 (file)
@@ -2142,7 +2142,9 @@ class assignment_base {
                 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
                 $output .= '<a href="'.$path.'" >'.$OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon')).s($filename).'</a>';
                 if ($CFG->enableportfolios && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
-                    $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $submission->id, 'fileid' => $file->get_id()), '/mod/assignment/locallib.php');
+                    $button->set_callback_options('assignment_portfolio_caller',
+                                                  array('id' => $this->cm->id, 'submissionid' => $submission->id, 'fileid' => $file->get_id()),
+                                                  'mod_assignment');
                     $button->set_format_by_file($file);
                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
                 }
@@ -2154,7 +2156,9 @@ class assignment_base {
                 }
             }
             if ($CFG->enableportfolios && count($files) > 1  && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
-                $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $submission->id), '/mod/assignment/locallib.php');
+                $button->set_callback_options('assignment_portfolio_caller',
+                                              array('id' => $this->cm->id, 'submissionid' => $submission->id),
+                                              'mod_assignment');
                 $output .= '<br />'  . $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
             }
         }
index f514e7f..6545edc 100644 (file)
@@ -100,7 +100,7 @@ class assignment_files implements renderable {
             $files = $fs->get_area_files($this->context->id, 'mod_assignment', $filearea, $itemid, "timemodified", false);
             if (count($files) >= 1 && has_capability('mod/assignment:exportownsubmission', $this->context)) {
                 $button = new portfolio_add_button();
-                $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $itemid), '/mod/assignment/locallib.php');
+                $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $itemid), 'mod_assignment');
                 $button->reset_formats();
                 $this->portfolioform = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
             }
@@ -117,7 +117,7 @@ class assignment_files implements renderable {
             if (!empty($CFG->enableportfolios)) {
                 $button = new portfolio_add_button();
                 if (has_capability('mod/assignment:exportownsubmission', $this->context)) {
-                    $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'fileid' => $file->get_id()), '/mod/assignment/locallib.php');
+                    $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'fileid' => $file->get_id()), 'mod_assignment');
                     $button->set_format_by_file($file);
                     $file->portfoliobutton = $button->to_html(PORTFOLIO_ADD_ICON_LINK);
                 }
index f3cd31b..a917d31 100644 (file)
@@ -117,7 +117,7 @@ class assignment_online extends assignment_base {
                     if ($CFG->enableportfolios) {
                         require_once($CFG->libdir . '/portfoliolib.php');
                         $button = new portfolio_add_button();
-                        $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id), '/mod/assignment/locallib.php');
+                        $button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id), 'mod_assignment');
                         $fs = get_file_storage();
                         if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', $this->filearea, $submission->id, "timemodified", false)) {
                             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
index 331cc96..35bd202 100644 (file)
                     'end'   => $end,
                 );
                 $button = new portfolio_add_button();
-                $button->set_callback_options('chat_portfolio_caller', $buttonoptions, '/mod/chat/locallib.php');
+                $button->set_callback_options('chat_portfolio_caller', $buttonoptions, 'mod_chat');
                 $button->render();
             }
             echo $OUTPUT->box_end();
                         'end'   => $sessionend,
                     );
                     $button = new portfolio_add_button();
-                    $button->set_callback_options('chat_portfolio_caller', $buttonoptions, '/mod/chat/locallib.php');
+                    $button->set_callback_options('chat_portfolio_caller', $buttonoptions, 'mod_chat');
                     $portfoliobutton = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
                     if (!empty($portfoliobutton)) {
                         echo '<br />' . $portfoliobutton;
     if (!empty($CFG->enableportfolios) && $canexportsess) {
         require_once($CFG->libdir . '/portfoliolib.php');
         $button = new portfolio_add_button();
-        $button->set_callback_options('chat_portfolio_caller', array('id' => $cm->id), '/mod/chat/locallib.php');
+        $button->set_callback_options('chat_portfolio_caller', array('id' => $cm->id), 'mod_chat');
         $button->render(null, get_string('addalltoportfolio', 'portfolio'));
     }
 
index fb0dc5f..a1a4703 100644 (file)
@@ -1234,7 +1234,7 @@ function data_print_template($template, $records, $data, $search='', $page=0, $r
                 || (data_isowner($record->id) && has_capability('mod/data:exportownentry', $context))))) {
             require_once($CFG->libdir . '/portfoliolib.php');
             $button = new portfolio_add_button();
-            $button->set_callback_options('data_portfolio_caller', array('id' => $cm->id, 'recordid' => $record->id), '/mod/data/locallib.php');
+            $button->set_callback_options('data_portfolio_caller', array('id' => $cm->id, 'recordid' => $record->id), 'mod_data');
             list($formats, $files) = data_portfolio_caller::formats($fields, $record);
             $button->set_formats($formats);
             $replacement[] = $button->to_html(PORTFOLIO_ADD_ICON_LINK);
index 5220e04..65e512d 100644 (file)
@@ -760,7 +760,7 @@ if ($showactivity) {
     if ($mode == '' && !empty($CFG->enableportfolios)) {
         require_once($CFG->libdir . '/portfoliolib.php');
         $button = new portfolio_add_button();
-        $button->set_callback_options('data_portfolio_caller', array('id' => $cm->id), '/mod/data/locallib.php');
+        $button->set_callback_options('data_portfolio_caller', array('id' => $cm->id), 'mod_data');
         if (data_portfolio_caller::has_files($data)) {
             $button->set_formats(array(PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_LEAP2A)); // no plain html for us
         }
index efbb8df..774e7b0 100644 (file)
     if (!empty($CFG->enableportfolios) && has_capability('mod/forum:exportdiscussion', $modcontext)) {
         require_once($CFG->libdir.'/portfoliolib.php');
         $button = new portfolio_add_button();
-        $button->set_callback_options('forum_portfolio_caller', array('discussionid' => $discussion->id), '/mod/forum/locallib.php');
+        $button->set_callback_options('forum_portfolio_caller', array('discussionid' => $discussion->id), 'mod_forum');
         $button = $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportdiscussion', 'mod_forum'));
         $buttonextraclass = '';
         if (empty($button)) {
index d39ad0e..822e177 100644 (file)
@@ -3347,7 +3347,7 @@ function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=fa
         $p = array('postid' => $post->id);
         require_once($CFG->libdir.'/portfoliolib.php');
         $button = new portfolio_add_button();
-        $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
+        $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
         if (empty($attachments)) {
             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
         } else {
@@ -4001,7 +4001,7 @@ function forum_print_attachments($post, $cm, $type) {
                 $output .= "<a href=\"$path\">$iconimage</a> ";
                 $output .= "<a href=\"$path\">".s($filename)."</a>";
                 if ($canexport) {
-                    $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
+                    $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
                     $button->set_format_by_file($file);
                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
                 }
@@ -4015,7 +4015,7 @@ function forum_print_attachments($post, $cm, $type) {
                     // Image attachments don't get printed as links
                     $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
                     if ($canexport) {
-                        $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
+                        $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
                         $button->set_format_by_file($file);
                         $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
                     }
@@ -4023,7 +4023,7 @@ function forum_print_attachments($post, $cm, $type) {
                     $output .= "<a href=\"$path\">$iconimage</a> ";
                     $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
                     if ($canexport) {
-                        $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
+                        $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
                         $button->set_format_by_file($file);
                         $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
                     }
index 773fd7f..8abcaea 100644 (file)
@@ -70,7 +70,7 @@ $exporturl = moodle_url::make_pluginfile_url($context->id, 'mod_glossary', 'expo
     if (!empty($CFG->enableportfolios) && $DB->count_records('glossary_entries', array('glossaryid' => $glossary->id))) {
         require_once($CFG->libdir . '/portfoliolib.php');
         $button = new portfolio_add_button();
-        $button->set_callback_options('glossary_full_portfolio_caller', array('id' => $cm->id), '/mod/glossary/locallib.php');
+        $button->set_callback_options('glossary_full_portfolio_caller', array('id' => $cm->id), 'mod_glossary');
         $button->render();
     }
     echo $OUTPUT->box_end();
index 49f4c3f..3c3f463 100644 (file)
@@ -1273,7 +1273,7 @@ function glossary_print_entry_icons($course, $cm, $glossary, $entry, $mode='',$h
     if (!empty($CFG->enableportfolios) && (has_capability('mod/glossary:exportentry', $context) || ($iscurrentuser && has_capability('mod/glossary:exportownentry', $context)))) {
         require_once($CFG->libdir . '/portfoliolib.php');
         $button = new portfolio_add_button();
-        $button->set_callback_options('glossary_entry_portfolio_caller',  array('id' => $cm->id, 'entryid' => $entry->id), '/mod/glossary/locallib.php');
+        $button->set_callback_options('glossary_entry_portfolio_caller',  array('id' => $cm->id, 'entryid' => $entry->id), 'mod_glossary');
 
         $filecontext = $context;
         if ($entry->sourceglossaryid == $cm->instance) {
index 8cca635..8a22e6f 100644 (file)
@@ -33,18 +33,18 @@ require_once($CFG->libdir . '/portfolio/exporter.php');
 require_once($CFG->libdir . '/portfolio/caller.php');
 require_once($CFG->libdir . '/portfolio/plugin.php');
 
-$dataid        = optional_param('id', 0, PARAM_INT);                          // id of partially completed export. corresponds to a record in portfolio_tempdata
-$type          = optional_param('type', null, PARAM_SAFEDIR);                 // if we're returning from an external system (postcontrol) for a single-export only plugin
-$cancel        = optional_param('cancel', 0, PARAM_RAW);                      // user has cancelled the request
-$cancelsure    = optional_param('cancelsure', 0, PARAM_BOOL);                 // make sure they confirm first
-$logreturn     = optional_param('logreturn', 0, PARAM_BOOL);                  // when cancelling, we can also come from the log page, rather than the caller
-$instanceid    = optional_param('instance', 0, PARAM_INT);                    // instanceof of configured portfolio plugin
-$courseid      = optional_param('course', 0, PARAM_INT);                      // courseid the data being exported belongs to (caller object should provide this later)
-$stage         = optional_param('stage', PORTFOLIO_STAGE_CONFIG, PARAM_INT);  // stage of the export we're at (stored in the exporter)
-$postcontrol   = optional_param('postcontrol', 0, PARAM_INT);                 // when returning from some bounce to an external system, this gets passed
-$callbackfile  = optional_param('callbackfile', null, PARAM_PATH);            // callback file eg /mod/forum/lib.php - the location of the exporting content
-$callbackclass = optional_param('callbackclass', null, PARAM_ALPHAEXT);       // callback class eg forum_portfolio_caller - the class to handle the exporting content.
-$callerformats = optional_param('callerformats', null, PARAM_TAGLIST);        // comma separated list of formats the specific place exporting content supports
+$dataid = optional_param('id', 0, PARAM_INT); // The ID of partially completed export, corresponds to a record in portfolio_tempdata.
+$type = optional_param('type', null, PARAM_SAFEDIR); // If we're returning from an external system (postcontrol) for a single-export only plugin.
+$cancel = optional_param('cancel', 0, PARAM_RAW); // User has cancelled the request.
+$cancelsure = optional_param('cancelsure', 0, PARAM_BOOL); // Make sure they confirm first.
+$logreturn = optional_param('logreturn', 0, PARAM_BOOL); // When cancelling, we can also come from the log page, rather than the caller.
+$instanceid = optional_param('instance', 0, PARAM_INT); // The instance of configured portfolio plugin.
+$courseid = optional_param('course', 0, PARAM_INT); // The courseid the data being exported belongs to (caller object should provide this later).
+$stage = optional_param('stage', PORTFOLIO_STAGE_CONFIG, PARAM_INT); // Stage of the export we're at (stored in the exporter).
+$postcontrol = optional_param('postcontrol', 0, PARAM_INT); // When returning from some bounce to an external system, this gets passed.
+$callbackcomponent = optional_param('callbackcomponent', null, PARAM_PATH); // Callback component eg mod_forum - the component of the exporting content.
+$callbackclass = optional_param('callbackclass', null, PARAM_ALPHAEXT); // Callback class eg forum_portfolio_caller - the class to handle the exporting content.
+$callerformats = optional_param('callerformats', null, PARAM_TAGLIST); // Comma separated list of formats the specific place exporting content supports.
 
 require_login();  // this is selectively called again with $course later when we know for sure which one we're in.
 $PAGE->set_context(get_system_context());
@@ -152,7 +152,7 @@ if (!empty($dataid)) {
 
     // we must be passed this from the caller, we cannot start a new export
     // without knowing information about what part of moodle we come from.
-    if (empty($callbackfile) || empty($callbackclass)) {
+    if (empty($callbackcomponent) || empty($callbackclass)) {
         debugging('no callback file or class');
         portfolio_exporter::print_expired_export();
     }
@@ -173,13 +173,10 @@ if (!empty($dataid)) {
             $callbackargs[substr($key, 3)] = $value;
         }
     }
-    // righto, now we have the callback args set up
-    // load up the caller file and class and tell it to set up all the data
-    // it needs
-    require_once($CFG->dirroot . $callbackfile);
-    if (!class_exists($callbackclass) || !is_subclass_of($callbackclass, 'portfolio_caller_base')) {
-        throw new portfolio_caller_exception('callbackclassinvalid', 'portfolio');
-    }
+
+    // Ensure that we found a file we can use, if not throw an exception.
+    portfolio_include_callback_file($callbackcomponent, $callbackclass);
+
     $caller = new $callbackclass($callbackargs);
     $caller->set('user', $USER);
     if ($formats = explode(',', $callerformats)) {
@@ -194,7 +191,7 @@ if (!empty($dataid)) {
     portfolio_export_pagesetup($PAGE, $caller); // this calls require_login($course) if it can..
 
     // finally! set up the exporter object with the portfolio instance, and caller information elements
-    $exporter = new portfolio_exporter($instance, $caller, $callbackfile);
+    $exporter = new portfolio_exporter($instance, $caller, $callbackcomponent);
 
     // set the export-specific variables, and save.
     $exporter->set('user', $USER);
index efc88ea..460df3d 100644 (file)
@@ -7,3 +7,20 @@ required changes:
 * The following methods must now be declared static for php5 compatibility:
     - admin_config_form
     - admin_config_validation
+
+=== 2.4 ===
+
+The set_callback_options function's third parameter has been changed from a file path
+to the component name - see MDL-33791. However, if any existing code passes a file path
+Moodle will attempt to obtain the component name from the file path provided. Also, the
+callback class should be located in the module's locallib.php file.
+
+Example of change:
+
+This:
+
+$button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'fileid' => $file->get_id()), '/mod/assignment/locallib.php');
+
+Now becomes:
+
+$button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'fileid' => $file->get_id()), 'mod_assignment');
\ No newline at end of file
index d9f6035..1fa544b 100644 (file)
@@ -117,7 +117,13 @@ if ($logcount > 0) {
     );
     $logs = $DB->get_records('portfolio_log', array('userid' => $USER->id), 'time DESC', '*', ($page * $perpage), $perpage);
     foreach ($logs as $log) {
-        require_once($CFG->dirroot . $log->caller_file);
+        if (!empty($log->caller_file)) {
+            portfolio_include_callback_file($log->caller_file);
+        } else if (!empty($log->caller_component)) {
+            portfolio_include_callback_file($log->caller_component);
+        } else { // Errrmahgerrrd - this should never happen. Skipping.
+            continue;
+        }
         $class = $log->caller_class;
         $pluginname = '';
         try {
index b6cffad..6e7cade 100644 (file)
@@ -30,7 +30,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 
-$version  = 2012110700.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2012110700.01;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes