diff --git a/lang/en_utf8/portfolio.php b/lang/en_utf8/portfolio.php index bff3bdc8b51..ec958abb5aa 100644 --- a/lang/en_utf8/portfolio.php +++ b/lang/en_utf8/portfolio.php @@ -13,6 +13,7 @@ $string['callercouldnotpackage'] = 'Failed to package up your data for export: o $string['cannotsetvisible'] = 'Cannot set this to visible - the plugin has been completely disabled because of a misconfiguration'; $string['configexport'] = 'Configure exported data'; $string['configplugin'] = 'Configure portfolio plugin'; +$string['confirmcancel'] = 'Are you sure you wish you cancel this export?'; $string['confirmexport'] = 'Please confirm this export'; $string['confirmsummary'] = 'Summary of your export'; $string['configure'] = 'Configure'; @@ -102,6 +103,9 @@ $string['nocallbackfile'] = 'Something in the module you\'re trying to export fr $string['nocallbackclass'] = 'Could not find the callback class to use ($a)'; $string['nocommonformats'] = 'No common formats between any available portfolio plugin and the calling location $a'; $string['noclassbeforeformats'] = 'You must set the callback class before calling set_formats in portfolio_button'; +$string['noinstanceyet'] = 'Not yet selected'; +$string['nologs'] = 'There are no logs to display!'; +$string['nomultipleexports'] = 'Sorry, but the portfolio destination ($a->plugin) doesn\'t support multiple exports at the same time. Please link\">finish the current one first and try again'; $string['nopermissions'] = 'Sorry but you do not have the required permissions to export files from this area'; $string['nonprimative'] = 'A non primative value was passed as a callback argument to portfolio_add_button. Refusing to continue. The key was $a->key and the value was $a->value'; $string['notexportable'] = 'Sorry, but the type of content you are trying to export is not exportable'; @@ -120,6 +124,7 @@ $string['save'] = 'Save'; $string['selectedformat'] = 'Selected export format'; $string['selectedwait'] = 'Selected to wait?'; $string['selectplugin'] = 'Select destination'; +$string['singleinstancenomultiallowed'] = 'Only a single portfolio plugin instance is available, it doesn\'t support multiple exports per session, and there\'s already an active export in the session using this plugin!'; $string['someinstancesdisabled'] = 'Some configured portfolio plugin instances have been disabled either because they are misconfigured or rely on something else that is'; $string['somepluginsdisabled'] = 'Some entire portfolio plugins have been disabled because they are either misconfigured or rely on something else that is:'; $string['sure'] = 'Are you sure you want to delete \'$a\'? This cannot be undone.'; diff --git a/lib/portfolio/exceptions.php b/lib/portfolio/exceptions.php index 3c249f0a6c2..dee46e5020c 100644 --- a/lib/portfolio/exceptions.php +++ b/lib/portfolio/exceptions.php @@ -59,11 +59,6 @@ class portfolio_export_exception extends portfolio_exception { // but I think if there's always an exception, we should clean up // rather than force the user to resolve the export later. $exporter->process_stage_cleanup(); - } else { - global $SESSION; - if (!empty($SESSION->portfolioexport)) { - debugging(get_string('exportexceptionnoexporter', 'portfolio')); - } } parent::__construct($errorcode, $module, $continue, $a); } diff --git a/lib/portfolio/exporter.php b/lib/portfolio/exporter.php index 46ea79be635..6d0c8c29a6d 100644 --- a/lib/portfolio/exporter.php +++ b/lib/portfolio/exporter.php @@ -89,12 +89,6 @@ class portfolio_exporter { */ private $id; - /** - * the session key during the export - * used to avoid hijacking transfers - */ - private $sesskey; - /** * array of stages that have had the portfolio plugin already steal control from them */ @@ -113,6 +107,16 @@ class portfolio_exporter { */ private $format; + /** + * queued - this is set after the event is triggered + */ + private $queued = false; + + /** + * expiry time - set the first time the object is saved out + */ + private $expirytime; + /** * construct a new exporter for use * @@ -286,6 +290,7 @@ class portfolio_exporter { if ($pluginobj || $callerobj || count($formats) > 1 || ($expectedtime != PORTFOLIO_TIME_LOW && $expectedtime != PORTFOLIO_TIME_FORCEQUEUE)) { $customdata = array( 'instance' => $this->instance, + 'id' => $this->id, 'plugin' => $pluginobj, 'caller' => $callerobj, 'userid' => $this->user->id, @@ -371,8 +376,9 @@ class portfolio_exporter { return true; } $strconfirm = get_string('confirmexport', 'portfolio'); - $yesurl = $CFG->wwwroot . '/portfolio/add.php?stage=' . PORTFOLIO_STAGE_QUEUEORWAIT; - $nourl = $CFG->wwwroot . '/portfolio/add.php?cancel=1'; + $baseurl = $CFG->wwwroot . '/portfolio/add.php?sesskey=' . sesskey() . '&id=' . $this->get('id'); + $yesurl = $baseurl . '&stage=' . PORTFOLIO_STAGE_QUEUEORWAIT; + $nourl = $baseurl . '&cancel=1'; $this->print_header('confirmexport'); echo $OUTPUT->box_start(); echo $OUTPUT->heading(get_string('confirmsummary', 'portfolio'), 4); @@ -420,11 +426,10 @@ class portfolio_exporter { * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. */ public function process_stage_queueorwait() { - global $SESSION; $wait = $this->instance->get_export_config('wait'); if (empty($wait)) { events_trigger('portfolio_send', $this->id); - unset($SESSION->portfolioexport); + $this->queued = true; return $this->process_stage_finished(true); } return true; @@ -465,10 +470,9 @@ class portfolio_exporter { * @return boolean whether or not to process the next stage. this is important as the control function is called recursively. */ public function process_stage_cleanup($pullok=false) { - global $CFG, $DB, $SESSION; + global $CFG, $DB; if (!$pullok && $this->get('instance') && !$this->get('instance')->is_push()) { - unset($SESSION->portfolioexport); return true; } if ($this->get('instance')) { @@ -478,7 +482,6 @@ class portfolio_exporter { $DB->delete_records('portfolio_tempdata', array('id' => $this->id)); $fs = get_file_storage(); $fs->delete_area_files(SYSCONTEXTID, 'portfolio_exporter', $this->id); - unset($SESSION->portfolioexport); return true; } @@ -590,11 +593,15 @@ class portfolio_exporter { * cancels a potfolio request and cleans up the tempdata * and redirects the user back to where they started */ - public function cancel_request() { + public function cancel_request($logreturn=false) { + global $CFG; if (!isset($this)) { return; } $this->process_stage_cleanup(true); + if ($logreturn) { + redirect($CFG->wwwroot . '/user/portfoliologs.php'); + } redirect($this->caller->get_return_url()); exit; } @@ -612,6 +619,7 @@ class portfolio_exporter { 'instance' => (empty($this->instance)) ? null : $this->instance->get('id'), ); $this->id = $DB->insert_record('portfolio_tempdata', $r); + $this->expirytime = $r->expirytime; $this->save(); // call again so that id gets added to the save data. } else { $r = $DB->get_record('portfolio_tempdata', array('id' => $this->id)); @@ -641,6 +649,13 @@ class portfolio_exporter { } require_once($CFG->dirroot . '/' . $exporter->callerfile); $exporter = unserialize(serialize($exporter)); + if (!$exporter->get('id')) { + // workaround for weird case + // where the id doesn't get saved between a new insert + // and the subsequent call that sets this field in the serialised data + $exporter->set('id', $id); + $exporter->save(); + } return $exporter; } @@ -669,14 +684,21 @@ class portfolio_exporter { * @throws portfolio_exception */ public function verify_rewaken($readonly=false) { - global $USER; - if ($this->get('user')->id != $USER->id) { + global $USER, $CFG; + if ($this->get('user')->id != $USER->id) { // make sure it belongs to the right user throw new portfolio_exception('notyours', 'portfolio'); } - if (!$readonly && !confirm_sesskey($this->get('sesskey'))) { - throw new portfolio_exception('confirmsesskeybad'); + if (!$readonly && $this->get('instance') && !$this->get('instance')->allows_multiple_exports() + && ($already = portfolio_exporter::existing_exports($this->get('user')->id, $this->get('instance')->get('plugin'))) + && array_shift(array_keys($already)) != $this->get('id') + ) { + $a = (object)array( + 'plugin' => $this->get('instance')->get('plugin'), + 'link' => $CFG->wwwroot . '/user/portfoliologs.php', + ); + throw new portfolio_exception('nomultipleexports', 'portfolio', '', $a); } - if (!$this->caller->check_permissions()) { + if (!$this->caller->check_permissions()) { // recall the caller permission check throw new portfolio_caller_exception('nopermissions', 'portfolio', $this->caller->get_return_url()); } } @@ -799,4 +821,37 @@ class portfolio_exporter { exit; } + /** + * return a list of current exports for the given user + * this will not go through and call rewaken_object, because it's heavy + * it's really just used to figure out what exports are currently happening. + * this is useful for plugins that don't support multiple exports per session + * + * @param int $userid the user to check for + * @param string $type (optional) the portfolio plugin to filter by + * + * @return array + */ + public static function existing_exports($userid, $type=null) { + global $DB; + $sql = 'SELECT t.*,t.instance,i.plugin,i.name FROM {portfolio_tempdata} t JOIN {portfolio_instance} i ON t.instance = i.id WHERE t.userid = ? '; + $values = array($userid); + if ($type) { + $sql .= ' AND i.plugin = ?'; + $values[] = $type; + } + return $DB->get_records_sql($sql, $values); + } + + /** + * Return an array of existing exports by type for a given user. + * This is much more lightweight than {@see existing_exports} because it only returns the types, rather than the whole serialised data + * so can be used for checking availability of multiple plugins at the same time. + */ + public static function existing_exports_by_plugin($userid) { + global $DB; + $sql = 'SELECT t.instance,i.plugin FROM {portfolio_tempdata} t JOIN {portfolio_instance} i ON t.instance = i.id WHERE t.userid = ? '; + $values = array($userid); + return $DB->get_records_sql_menu($sql, $values); + } } diff --git a/lib/portfolio/forms.php b/lib/portfolio/forms.php index 6559eff60fd..61ce6dfbab1 100644 --- a/lib/portfolio/forms.php +++ b/lib/portfolio/forms.php @@ -45,9 +45,11 @@ final class portfolio_export_form extends moodleform { $mform =& $this->_form; $mform->addElement('hidden', 'stage', PORTFOLIO_STAGE_CONFIG); - $mform->setType('stage', PARAM_INT); + $mform->addElement('hidden', 'id', $this->_customdata['id']); $mform->addElement('hidden', 'instance', $this->_customdata['instance']->get('id')); $mform->setType('instance', PARAM_INT); + $mform->setType('stage', PARAM_INT); + $mform->setType('id', PARAM_INT); if (array_key_exists('formats', $this->_customdata) && is_array($this->_customdata['formats'])) { if (count($this->_customdata['formats']) > 1) { @@ -260,12 +262,16 @@ class portfolio_instance_select extends moodleform { true, true ); + // TODO maybe add on some information to the user if they're already exporting + // and some of the options were skipped because they are for plugins that don't support + // multiple exports per session if (empty($options)) { debugging('noavailableplugins', 'portfolio'); return false; } $mform =& $this->_form; $mform->addElement('select', 'instance', get_string('selectplugin', 'portfolio'), $options); + $mform->addElement('hidden', 'id', $this->_customdata['id']); $this->add_action_buttons(true, get_string('next')); } } diff --git a/lib/portfolio/plugin.php b/lib/portfolio/plugin.php index e9cc4d0a79c..a8a91349bbb 100644 --- a/lib/portfolio/plugin.php +++ b/lib/portfolio/plugin.php @@ -717,6 +717,20 @@ abstract class portfolio_plugin_base { public static function mnet_publishes() { return array(); } + + /** + * whether this plugin supports multiple exports in the same session + * most plugins should handle this, but some that require a redirect for authentication + * and then don't support dynamically constructed urls to return to (eg box.net) + * need to override this to return false. + * this means that moodle will prevent multiple exports of this *type* of plugin + * occurring in the same session. + * + * @return boolean + */ + public static function allows_multiple_exports() { + return true; + } } /** diff --git a/lib/portfoliolib.php b/lib/portfoliolib.php index 5ed6b958140..7cd706f7f4e 100644 --- a/lib/portfoliolib.php +++ b/lib/portfoliolib.php @@ -66,7 +66,6 @@ require_once($CFG->libdir . '/portfolio/caller.php'); // the base classes f */ class portfolio_add_button { - private $alreadyexporting; private $callbackclass; private $callbackargs; private $callbackfile; @@ -87,14 +86,11 @@ class portfolio_add_button { */ public function __construct($options=null) { global $SESSION, $CFG; - if (isset($SESSION->portfolioexport)) { - $this->alreadyexporting = true; - return; - } $this->instances = portfolio_instances(); if (empty($options)) { return true; } + $constructoroptions = array('callbackclass', 'callbackargs', 'callbackfile', 'formats'); foreach ((array)$options as $key => $value) { if (!in_array($key, $constructoroptions)) { throw new portfolio_button_exception('invalidbuttonproperty', 'portfolio', $key); @@ -117,9 +113,6 @@ class portfolio_add_button { * this path should be relative (ie, not include) dirroot, eg '/mod/forum/lib.php' */ public function set_callback_options($class, array $argarray, $file=null) { - if ($this->alreadyexporting) { - return; - } global $CFG; if (empty($file)) { $backtrace = debug_backtrace(); @@ -159,9 +152,6 @@ class portfolio_add_button { * {@see portfolio_format_from_file} for how to get the appropriate formats to pass here for uploaded files. */ public function set_formats($formats=null) { - if ($this->alreadyexporting) { - return; - } if (is_string($formats)) { $formats = array($formats); } @@ -198,9 +188,6 @@ class portfolio_add_button { * this is whole string, not key. optional, defaults to 'Add to portfolio'; */ public function to_html($format=null, $addstr=null) { - if ($this->alreadyexporting) { - return $this->already_exporting($format, $addstr); - } global $CFG, $COURSE, $OUTPUT; if (!$this->is_renderable()) { return; @@ -235,7 +222,7 @@ class portfolio_add_button { if (count($this->instances) == 1) { $tmp = array_values($this->instances); $instance = $tmp[0]; - //$instance = array_shift($this->instances); + $formats = portfolio_supported_formats_intersect($this->formats, $instance->supported_formats()); if (count($formats) == 0) { // bail. no common formats. @@ -247,6 +234,10 @@ class portfolio_add_button { debugging(get_string('instancemisconfigured', 'portfolio', get_string($error[$instance->get('id')], 'portfolio_' . $instance->get('plugin')))); return; } + if (!$instance->allows_multiple_exports() && $already = portfolio_exporter::existing_exports($USER->id, $instance->get('plugin'))) { + debugging(get_string('singleinstancenomultiallowed', 'portfolio')); + return; + } $formoutput .= "\n" . ''; $linkoutput .= '&instance=' . $instance->get('id'); } @@ -336,33 +327,6 @@ class portfolio_add_button { public function get_callbackclass() { return $this->callbackclass; } - - private function already_exporting($format, $addstr) { - global $CFG, $OUTPUT; - $url = $CFG->wwwroot . '/portfolio/already.php'; - $icon = $OUTPUT->old_icon_url('t/portfoliono') . ''; - $alt = get_string('alreadyalt', 'portfolio'); - if (empty($format)) { - $format = PORTFOLIO_ADD_FULL_FORM; - } - if (empty($addstr)) { - $addstr = get_string('addtoportfolio', 'portfolio'); - } - switch ($format) { - case PORTFOLIO_ADD_FULL_FORM: - return '
'; - case PORTFOLIO_ADD_ICON_FORM: - case PORTFOLIO_ADD_ICON_LINK: - return '