Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * This file contains the base classes for portfolio plugins to inherit from:
  19   *
  20   * portfolio_plugin_pull_base and portfolio_plugin_push_base
  21   * which both in turn inherit from portfolio_plugin_base.
  22   * {@link http://docs.moodle.org/dev/Writing_a_Portfolio_Plugin}
  23   *
  24   * @package    core_portfolio
  25   * @copyright  2008 Penny Leach <penny@catalyst.net.nz>,
  26   *             Martin Dougiamas <http://dougiamas.com>
  27   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  28   */
  29  
  30  defined('MOODLE_INTERNAL') || die();
  31  
  32  /**
  33   * The base class for portfolio plugins.
  34   *
  35   * All plugins must subclass this
  36   * either via portfolio_plugin_pull_base or portfolio_plugin_push_base
  37   * @see portfolio_plugin_pull_base
  38   * @see portfolio_plugin_push_base
  39   *
  40   * @package core_portfolio
  41   * @category portfolio
  42   * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
  43   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  abstract class portfolio_plugin_base {
  46  
  47      /** @var bool whether this object needs writing out to the database */
  48      protected $dirty;
  49  
  50      /** @var integer id of instance */
  51      protected $id;
  52  
  53      /** @var string name of instance */
  54      protected $name;
  55  
  56      /** @var string plugin this instance belongs to */
  57      protected $plugin;
  58  
  59      /** @var bool whether this instance is visible or not */
  60      protected $visible;
  61  
  62      /** @var array admin configured config use {@link set_config} and {@get_config} to access */
  63      protected $config;
  64  
  65      /** @var array user config cache. keyed on userid and then on config field => value use {@link get_user_config} and {@link set_user_config} to access. */
  66      protected $userconfig;
  67  
  68      /** @var array export config during export use {@link get_export_config} and {@link set export_config} to access. */
  69      protected $exportconfig;
  70  
  71      /** @var stdClass user currently exporting data */
  72      protected $user;
  73  
  74      /** @var stdClass a reference to the exporter object */
  75      protected $exporter;
  76  
  77      /**
  78       * Array of formats this portfolio supports
  79       * the intersection of what this function returns
  80       * and what the caller supports will be used.
  81       * Use the constants PORTFOLIO_FORMAT_*
  82       *
  83       * @return array list of formats
  84       */
  85      public function supported_formats() {
  86          return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICH);
  87      }
  88  
  89      /**
  90       * Override this if you are supporting the 'file' type (or a subformat)
  91       * but have restrictions on mimetypes
  92       *
  93       * @param string $mimetype file type or subformat
  94       * @return bool
  95       */
  96      public static function file_mime_check($mimetype) {
  97          return true;
  98      }
  99  
 100  
 101      /**
 102       * How long does this reasonably expect to take..
 103       * Should we offer the user the option to wait..
 104       * This is deliberately nonstatic so it can take filesize into account
 105       *
 106       * @param string $callertime - what the caller thinks
 107       *                             the portfolio plugin instance
 108       *                             is given the final say
 109       *                             because it might be (for example) download.
 110       */
 111      public abstract function expected_time($callertime);
 112  
 113      /**
 114       * Is this plugin push or pull.
 115       * If push, cleanup will be called directly after send_package
 116       * If not, cleanup will be called after portfolio/file.php is requested
 117       */
 118      public abstract function is_push();
 119  
 120      /**
 121       * Returns the user-friendly name for this plugin.
 122       * Usually just get_string('pluginname', 'portfolio_something')
 123       */
 124      public static function get_name() {
 125          throw new coding_exception('get_name() method needs to be overridden in each subclass of portfolio_plugin_base');
 126      }
 127  
 128      /**
 129       * Check sanity of plugin.
 130       * If this function returns something non empty, ALL instances of your plugin
 131       * will be set to invisble and not be able to be set back until it's fixed
 132       *
 133       * @return string|int|bool - string = error string KEY (must be inside portfolio_$yourplugin) or 0/false if you're ok
 134       */
 135      public static function plugin_sanity_check() {
 136          return 0;
 137      }
 138  
 139      /**
 140       * Check sanity of instances.
 141       * If this function returns something non empty, the instance will be
 142       * set to invislbe and not be able to be set back until it's fixed.
 143       *
 144       * @return int|string|bool - string = error string KEY (must be inside portfolio_$yourplugin) or 0/false if you're ok
 145       */
 146      public function instance_sanity_check() {
 147          return 0;
 148      }
 149  
 150      /**
 151       * Does this plugin need any configuration by the administrator?
 152       * If you override this to return true,
 153       * you <b>must</b> implement admin_config_form.
 154       * @see admin_config_form
 155       *
 156       * @return bool
 157       */
 158      public static function has_admin_config() {
 159          return false;
 160      }
 161  
 162      /**
 163       * Can this plugin be configured by the user in their profile?
 164       * If you override this to return true,
 165       * you <b>must</b> implement user_config_form
 166       * @see user_config_form
 167       *
 168       * @return bool
 169       */
 170      public function has_user_config() {
 171          return false;
 172      }
 173  
 174      /**
 175       * Does this plugin need configuration during export time?
 176       * If you override this to return true,
 177       * you <b>must</b> implement export_config_form.
 178       * @see export_config_form
 179       *
 180       * @return bool
 181       */
 182      public function has_export_config() {
 183          return false;
 184      }
 185  
 186      /**
 187       * Just like the moodle form validation function.
 188       * This is passed in the data array from the form
 189       * and if a non empty array is returned, form processing will stop.
 190       *
 191       * @param array $data data from form.
 192       */
 193      public function export_config_validation(array $data) {}
 194  
 195      /**
 196       * Just like the moodle form validation function.
 197       * This is passed in the data array from the form
 198       * and if a non empty array is returned, form processing will stop.
 199       *
 200       * @param array $data data from form.
 201       */
 202      public function user_config_validation(array $data) {}
 203  
 204      /**
 205       * Sets the export time config from the moodle form.
 206       * You can also use this to set export config that
 207       * isn't actually controlled by the user.
 208       * Eg: things that your subclasses want to keep in state
 209       * across the export.
 210       * Keys must be in get_allowed_export_config
 211       * This is deliberately not final (see boxnet plugin)
 212       * @see get_allowed_export_config
 213       *
 214       * @param array $config named array of config items to set.
 215       */
 216      public function set_export_config($config) {
 217          $allowed = array_merge(
 218              array('wait', 'hidewait', 'format', 'hideformat'),
 219              $this->get_allowed_export_config()
 220          );
 221          foreach ($config as $key => $value) {
 222              if (!in_array($key, $allowed)) {
 223                  $a = (object)array('property' => $key, 'class' => get_class($this));
 224                  throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', null, $a);
 225              }
 226              $this->exportconfig[$key] = $value;
 227          }
 228      }
 229  
 230      /**
 231       * Gets an export time config value.
 232       * Subclasses should not override this.
 233       *
 234       * @param string $key field to fetch
 235       * @return null|string config value
 236       */
 237      public final function get_export_config($key) {
 238          $allowed = array_merge(
 239              array('hidewait', 'wait', 'format', 'hideformat'),
 240              $this->get_allowed_export_config()
 241          );
 242          if (!in_array($key, $allowed)) {
 243              $a = (object)array('property' => $key, 'class' => get_class($this));
 244              throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', null, $a);
 245          }
 246          if (!array_key_exists($key, $this->exportconfig)) {
 247              return null;
 248          }
 249          return $this->exportconfig[$key];
 250      }
 251  
 252      /**
 253       * After the user submits their config,
 254       * they're given a confirm screen
 255       * summarising what they've chosen.
 256       * This function should return a table of nice strings => values
 257       * of what they've chosen
 258       * to be displayed in a table.
 259       *
 260       * @return bool
 261       */
 262      public function get_export_summary() {
 263          return false;
 264      }
 265  
 266      /**
 267       * Called after the caller has finished having control
 268       * of its prepare_package function.
 269       * This function should read all the files from the portfolio
 270       * working file area and zip them and send them or whatever it wants.
 271       * get_tempfiles to get the list of files.
 272       * @see get_tempfiles
 273       *
 274       */
 275      public abstract function prepare_package();
 276  
 277      /**
 278       * This is the function that is responsible for sending
 279       * the package to the remote system,
 280       * or whatever request is necessary to initiate the transfer.
 281       *
 282       * @return bool success
 283       */
 284      public abstract function send_package();
 285  
 286  
 287      /**
 288       * Once everything is done and the user
 289       * has the finish page displayed to them.
 290       * The base class takes care of printing them
 291       * "return to where you are" or "continue to portfolio" links.
 292       * This function allows for exta finish options from the plugin
 293       *
 294       * @return bool
 295       */
 296      public function get_extra_finish_options() {
 297          return false;
 298      }
 299  
 300      /**
 301       * The url for the user to continue to their portfolio
 302       * during the lifecycle of the request
 303       */
 304      public abstract function get_interactive_continue_url();
 305  
 306      /**
 307       * The url to save in the log as the continue url.
 308       * This is passed through resolve_static_continue_url()
 309       * at display time to the user.
 310       *
 311       * @return string
 312       */
 313      public function get_static_continue_url() {
 314          return $this->get_interactive_continue_url();
 315      }
 316  
 317      /**
 318       * Override this function if you need to add something on to the url
 319       * for post-export continues (eg from the log page).
 320       * Mahara does this, for example, to start a jump session.
 321       *
 322       * @param string $url static continue url
 323       * @return string
 324       */
 325      public function resolve_static_continue_url($url) {
 326          return $url;
 327      }
 328  
 329      /**
 330       * mform to display to the user in their profile
 331       * if your plugin can't be configured by the user,
 332       * @see has_user_config.
 333       * Don't bother overriding this function
 334       *
 335       * @param moodleform $mform passed by reference, add elements to it
 336       */
 337      public function user_config_form(&$mform) {}
 338  
 339      /**
 340       * mform to display to the admin configuring the plugin.
 341       * If your plugin can't be configured by the admin,
 342       * @see has_admin_config
 343       * Don't bother overriding this function.
 344       * This function can be called statically or non statically,
 345       * depending on whether it's creating a new instance (statically),
 346       * or editing an existing one (non statically)
 347       *
 348       * @param moodleform $mform passed by reference, add elements to it.
 349       */
 350      public static function admin_config_form(&$mform) {}
 351  
 352      /**
 353       * Just like the moodle form validation function,
 354       * this is passed in the data array from the form
 355       * and if a non empty array is returned, form processing will stop.
 356       *
 357       * @param array $data data from form.
 358       */
 359      public static function admin_config_validation($data) {}
 360  
 361      /**
 362       * mform to display to the user exporting data using this plugin.
 363       * If your plugin doesn't need user input at this time,
 364       * @see has_export_config.
 365       * Don't bother overrideing this function
 366       *
 367       * @param moodleform $mform passed by reference, add elements to it.
 368       */
 369      public function export_config_form(&$mform) {}
 370  
 371      /**
 372       * Override this if your plugin doesn't allow multiple instances
 373       *
 374       * @return bool
 375       */
 376      public static function allows_multiple_instances() {
 377          return true;
 378      }
 379  
 380      /**
 381       * If at any point the caller wants to steal control,
 382       * it can, by returning something that isn't false
 383       * in this function
 384       * The controller will redirect to whatever url
 385       * this function returns.
 386       * Afterwards, you can redirect back to portfolio/add.php?postcontrol=1
 387       * and post_control is called before the rest of the processing
 388       * for the stage is done,
 389       * @see post_control
 390       *
 391       * @param int $stage to steal control *before* (see constants PARAM_STAGE_*}
 392       * @return bool
 393       */
 394      public function steal_control($stage) {
 395          return false;
 396      }
 397  
 398      /**
 399       * After a plugin has elected to steal control,
 400       * and control returns to portfolio/add.php|postcontrol=1,
 401       * this function is called, and passed the stage that was stolen control from
 402       * and the request (get and post but not cookie) parameters.
 403       * This is useful for external systems that need to redirect the user back
 404       * with some extra data in the url (like auth tokens etc)
 405       * for an example implementation, see boxnet portfolio plugin.
 406       *
 407       * @param int $stage the stage before control was stolen
 408       * @param array $params a merge of $_GET and $_POST
 409       */
 410      public function post_control($stage, $params) { }
 411  
 412      /**
 413       * This function creates a new instance of a plugin
 414       * saves it in the database, saves the config
 415       * and returns it.
 416       * You shouldn't need to override it
 417       * unless you're doing something really funky
 418       *
 419       * @param string $plugin portfolio plugin to create
 420       * @param string $name name of new instance
 421       * @param array $config what the admin config form returned
 422       * @return object subclass of portfolio_plugin_base
 423       */
 424      public static function create_instance($plugin, $name, $config) {
 425          global $DB, $CFG;
 426          $new = (object)array(
 427              'plugin' => $plugin,
 428              'name'   => $name,
 429          );
 430          if (!portfolio_static_function($plugin, 'allows_multiple_instances')) {
 431              // check we don't have one already
 432              if ($DB->record_exists('portfolio_instance', array('plugin' => $plugin))) {
 433                  throw new portfolio_exception('multipleinstancesdisallowed', 'portfolio', '', $plugin);
 434              }
 435          }
 436          $newid = $DB->insert_record('portfolio_instance', $new);
 437          require_once($CFG->dirroot . '/portfolio/' . $plugin . '/lib.php');
 438          $classname = 'portfolio_plugin_'  . $plugin;
 439          $obj = new $classname($newid);
 440          $obj->set_config($config);
 441          $obj->save();
 442          return $obj;
 443      }
 444  
 445      /**
 446       * Construct a plugin instance.
 447       * Subclasses should not need to override this unless they're doing something special
 448       * and should call parent::__construct afterwards.
 449       *
 450       * @param int $instanceid id of plugin instance to construct
 451       * @param mixed $record stdclass object or named array - use this if you already have the record to avoid another query
 452       * @return portfolio_plugin_base
 453       */
 454      public function __construct($instanceid, $record=null) {
 455          global $DB;
 456          if (!$record) {
 457              if (!$record = $DB->get_record('portfolio_instance', array('id' => $instanceid))) {
 458                  throw new portfolio_exception('invalidinstance', 'portfolio');
 459              }
 460          }
 461          foreach ((array)$record as $key =>$value) {
 462              if (property_exists($this, $key)) {
 463                  $this->{$key} = $value;
 464              }
 465          }
 466          $this->config = new StdClass;
 467          $this->userconfig = array();
 468          $this->exportconfig = array();
 469          foreach ($DB->get_records('portfolio_instance_config', array('instance' => $instanceid)) as $config) {
 470              $this->config->{$config->name} = $config->value;
 471          }
 472          $this->init();
 473          return $this;
 474      }
 475  
 476      /**
 477       * Called after __construct - allows plugins to perform initialisation tasks
 478       * without having to override the constructor.
 479       */
 480      protected function init() { }
 481  
 482      /**
 483       * A list of fields that can be configured per instance.
 484       * This is used for the save handlers of the config form
 485       * and as checks in set_config and get_config.
 486       *
 487       * @return array array of strings (config item names)
 488       */
 489      public static function get_allowed_config() {
 490          return array();
 491      }
 492  
 493      /**
 494       * A list of fields that can be configured by the user.
 495       * This is used for the save handlers in the config form
 496       * and as checks in set_user_config and get_user_config.
 497       *
 498       * @return array array of strings (config field names)
 499       */
 500      public function get_allowed_user_config() {
 501          return array();
 502      }
 503  
 504      /**
 505       * A list of fields that can be configured by the user.
 506       * This is used for the save handlers in the config form
 507       * and as checks in set_export_config and get_export_config.
 508       *
 509       * @return array array of strings (config field names)
 510       */
 511      public function get_allowed_export_config() {
 512          return array();
 513      }
 514  
 515      /**
 516       * Saves (or updates) the config stored in portfolio_instance_config.
 517       * You shouldn't need to override this unless you're doing something funky.
 518       *
 519       * @param array $config array of config items.
 520       */
 521      public final function set_config($config) {
 522          global $DB;
 523          foreach ($config as $key => $value) {
 524              // try set it in $this first
 525              try {
 526                  $this->set($key, $value);
 527                  continue;
 528              } catch (portfolio_exception $e) { }
 529              if (!in_array($key, $this->get_allowed_config())) {
 530                  $a = (object)array('property' => $key, 'class' => get_class($this));
 531                  throw new portfolio_export_exception($this->get('exporter'), 'invalidconfigproperty', 'portfolio', null, $a);
 532              }
 533              if (!isset($this->config->{$key})) {
 534                  $DB->insert_record('portfolio_instance_config', (object)array(
 535                      'instance' => $this->id,
 536                      'name' => $key,
 537                      'value' => $value,
 538                  ));
 539              } else if ($this->config->{$key} != $value) {
 540                  $DB->set_field('portfolio_instance_config', 'value', $value, array('name' => $key, 'instance' => $this->id));
 541              }
 542              $this->config->{$key} = $value;
 543          }
 544      }
 545  
 546      /**
 547       * Gets the value of a particular config item
 548       *
 549       * @param string $key key to fetch
 550       * @return null|mixed the corresponding value
 551       */
 552      public final function get_config($key) {
 553          if (!in_array($key, $this->get_allowed_config())) {
 554              $a = (object)array('property' => $key, 'class' => get_class($this));
 555              throw new portfolio_export_exception($this->get('exporter'), 'invalidconfigproperty', 'portfolio', null, $a);
 556          }
 557          if (isset($this->config->{$key})) {
 558              return $this->config->{$key};
 559          }
 560          return null;
 561      }
 562  
 563      /**
 564       * Get the value of a config item for a particular user.
 565       *
 566       * @param string $key key to fetch
 567       * @param int $userid id of user (defaults to current)
 568       * @return string the corresponding value
 569       *
 570       */
 571      public final function get_user_config($key, $userid=0) {
 572          global $DB;
 573  
 574          if (empty($userid)) {
 575              $userid = $this->user->id;
 576          }
 577  
 578          if ($key != 'visible') { // handled by the parent class
 579              if (!in_array($key, $this->get_allowed_user_config())) {
 580                  $a = (object)array('property' => $key, 'class' => get_class($this));
 581                  throw new portfolio_export_exception($this->get('exporter'), 'invaliduserproperty', 'portfolio', null, $a);
 582              }
 583          }
 584          if (!array_key_exists($userid, $this->userconfig)) {
 585              $this->userconfig[$userid] = (object)array_fill_keys(array_merge(array('visible'), $this->get_allowed_user_config()), null);
 586              foreach ($DB->get_records('portfolio_instance_user', array('instance' => $this->id, 'userid' => $userid)) as $config) {
 587                  $this->userconfig[$userid]->{$config->name} = $config->value;
 588              }
 589          }
 590          if ($this->userconfig[$userid]->visible === null) {
 591              $this->set_user_config(array('visible' => 1), $userid);
 592          }
 593          return $this->userconfig[$userid]->{$key};
 594  
 595      }
 596  
 597      /**
 598       * Sets config options for a given user.
 599       *
 600       * @param array $config array containing key/value pairs to set
 601       * @param int $userid userid to set config for (defaults to current)
 602       *
 603       */
 604      public final function set_user_config($config, $userid=0) {
 605          global $DB;
 606  
 607          if (empty($userid)) {
 608              $userid = $this->user->id;
 609          }
 610  
 611          foreach ($config as $key => $value) {
 612              if ($key != 'visible' && !in_array($key, $this->get_allowed_user_config())) {
 613                  $a = (object)array('property' => $key, 'class' => get_class($this));
 614                  throw new portfolio_export_exception($this->get('exporter'), 'invaliduserproperty', 'portfolio', null, $a);
 615              }
 616              if (!$existing = $DB->get_record('portfolio_instance_user', array('instance'=> $this->id, 'userid' => $userid, 'name' => $key))) {
 617                  $DB->insert_record('portfolio_instance_user', (object)array(
 618                      'instance' => $this->id,
 619                      'name' => $key,
 620                      'value' => $value,
 621                      'userid' => $userid,
 622                  ));
 623              } else if ($existing->value != $value) {
 624                  $DB->set_field('portfolio_instance_user', 'value', $value, array('name' => $key, 'instance' => $this->id, 'userid' => $userid));
 625              }
 626              $this->userconfig[$userid]->{$key} = $value;
 627          }
 628  
 629      }
 630  
 631      /**
 632       * Generic getter for properties belonging to this instance
 633       * <b>outside</b> the subclasses
 634       * like name, visible etc.
 635       *
 636       * @param string $field property name
 637       * @return array|string|int|boolean value of the field
 638       */
 639      public final function get($field) {
 640          // This is a legacy change to the way files are get/set.
 641          // We now only set $this->file to the id of the \stored_file. So, we need to convert that id back to a \stored_file here.
 642          if ($field === 'file') {
 643              return $this->get_file();
 644          }
 645          if (property_exists($this, $field)) {
 646              return $this->{$field};
 647          }
 648          $a = (object)array('property' => $field, 'class' => get_class($this));
 649          throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', null, $a);
 650      }
 651  
 652      /**
 653       * Generic setter for properties belonging to this instance
 654       * <b>outside</b> the subclass
 655       * like name, visible, etc.
 656       *
 657       * @param string $field property's name
 658       * @param string $value property's value
 659       * @return bool
 660       */
 661      public final function set($field, $value) {
 662          // This is a legacy change to the way files are get/set.
 663          // Make sure we never save the \stored_file object. Instead, use the id from $file->get_id() - set_file() does this for us.
 664          if ($field === 'file') {
 665              $this->set_file($value);
 666              return true;
 667          }
 668          if (property_exists($this, $field)) {
 669              $this->{$field} =& $value;
 670              $this->dirty = true;
 671              return true;
 672          }
 673          $a = (object)array('property' => $field, 'class' => get_class($this));
 674          if ($this->get('exporter')) {
 675              throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', null, $a);
 676          }
 677          throw new portfolio_exception('invalidproperty', 'portfolio', null, $a); // this happens outside export (eg admin settings)
 678  
 679      }
 680  
 681      /**
 682       * Saves stuff that's been stored in the object to the database.
 683       * You shouldn't need to override this
 684       * unless you're doing something really funky.
 685       * and if so, call parent::save when you're done.
 686       *
 687       * @return bool
 688       */
 689      public function save() {
 690          global $DB;
 691          if (!$this->dirty) {
 692              return true;
 693          }
 694          $fordb = new StdClass();
 695          foreach (array('id', 'name', 'plugin', 'visible') as $field) {
 696              $fordb->{$field} = $this->{$field};
 697          }
 698          $DB->update_record('portfolio_instance', $fordb);
 699          $this->dirty = false;
 700          return true;
 701      }
 702  
 703      /**
 704       * Deletes everything from the database about this plugin instance.
 705       * You shouldn't need to override this unless you're storing stuff
 706       * in your own tables.  and if so, call parent::delete when you're done.
 707       *
 708       * @return bool
 709       */
 710      public function delete() {
 711          global $DB;
 712          $DB->delete_records('portfolio_instance_config', array('instance' => $this->get('id')));
 713          $DB->delete_records('portfolio_instance_user', array('instance' => $this->get('id')));
 714          $DB->delete_records('portfolio_tempdata', array('instance' => $this->get('id')));
 715          $DB->delete_records('portfolio_instance', array('id' => $this->get('id')));
 716          $this->dirty = false;
 717          return true;
 718      }
 719  
 720      /**
 721       * Perform any required cleanup functions
 722       *
 723       * @return bool
 724       */
 725      public function cleanup() {
 726          return true;
 727      }
 728  
 729      /**
 730       * Whether this plugin supports multiple exports in the same session
 731       * most plugins should handle this, but some that require a redirect for authentication
 732       * and then don't support dynamically constructed urls to return to (eg box.net)
 733       * need to override this to return false.
 734       * This means that moodle will prevent multiple exports of this *type* of plugin
 735       * occurring in the same session.
 736       *
 737       * @return bool
 738       */
 739      public static function allows_multiple_exports() {
 740          return true;
 741      }
 742  
 743      /**
 744       * Return a string to put at the header summarising this export
 745       * by default, just the plugin instance name
 746       *
 747       * @return string
 748       */
 749      public function heading_summary() {
 750          return get_string('exportingcontentto', 'portfolio', $this->name);
 751      }
 752  }
 753  
 754  /**
 755   * Class to inherit from for 'push' type plugins
 756   *
 757   * Eg: those that send the file via a HTTP post or whatever
 758   *
 759   * @package core_portfolio
 760   * @category portfolio
 761   * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
 762   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 763   */
 764  abstract class portfolio_plugin_push_base extends portfolio_plugin_base {
 765  
 766      /**
 767       * Get the capability to push
 768       *
 769       * @return bool
 770       */
 771      public function is_push() {
 772          return true;
 773      }
 774  }
 775  
 776  /**
 777   * Class to inherit from for 'pull' type plugins.
 778   *
 779   * Eg: those that write a file and wait for the remote system to request it
 780   * from portfolio/file.php.
 781   * If you're using this you must do $this->set('file', $file) so that it can be served.
 782   *
 783   * @package core_portfolio
 784   * @category portfolio
 785   * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
 786   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 787   */
 788  abstract class portfolio_plugin_pull_base extends portfolio_plugin_base {
 789  
 790      /** @var int $file the id of a single file */
 791      protected $file;
 792  
 793      /**
 794       * return the enablelity to push
 795       *
 796       * @return bool
 797       */
 798      public function is_push() {
 799          return false;
 800      }
 801  
 802      /**
 803       * The base part of the download file url to pull files from
 804       * your plugin might need to add &foo=bar on the end
 805       * @see verify_file_request_params
 806       *
 807       * @return string the url
 808       */
 809      public function get_base_file_url() {
 810          global $CFG;
 811          return $CFG->wwwroot . '/portfolio/file.php?id=' . $this->exporter->get('id');
 812      }
 813  
 814      /**
 815       * Before sending the file when the pull is requested, verify the request parameters.
 816       * These might include a token of some sort of whatever
 817       *
 818       * @param array $params request parameters (POST wins over GET)
 819       */
 820      public abstract function verify_file_request_params($params);
 821  
 822      /**
 823       * Called from portfolio/file.php.
 824       * This function sends the stored file out to the browser.
 825       * The default is to just use send_stored_file,
 826       * but other implementations might do something different,
 827       * for example, send back the file base64 encoded and encrypted
 828       * mahara does this but in the response to an xmlrpc request
 829       * rather than through file.php
 830       */
 831      public function send_file() {
 832          $file = $this->get('file');
 833          if (!($file instanceof stored_file)) {
 834              throw new portfolio_export_exception($this->get('exporter'), 'filenotfound', 'portfolio');
 835          }
 836          // don't die(); afterwards, so we can clean up.
 837          send_stored_file($file, 0, 0, true, array('dontdie' => true));
 838          $this->get('exporter')->log_transfer();
 839      }
 840  
 841      /**
 842       * Sets the $file instance var to the id of the supplied \stored_file.
 843  
 844       * This helper allows the $this->get('file') call to return a \stored_file, but means that we only ever record an id reference
 845       * in the $file instance var.
 846       *
 847       * @param \stored_file $file The stored_file instance.
 848       * @return void
 849       */
 850      protected function set_file(\stored_file $file) {
 851          $fileid = $file->get_id();
 852          if (empty($fileid)) {
 853              debugging('stored_file->id should not be empty');
 854              $this->file = null;
 855          } else {
 856              $this->file = $fileid;
 857          }
 858      }
 859  
 860      /**
 861       * Gets the \stored_file object from the file id in the $file instance var.
 862       *
 863       * @return stored_file|null the \stored_file object if it exists, null otherwise.
 864       */
 865      protected function get_file() {
 866          if (!$this->file) {
 867              return null;
 868          }
 869          // The get_file_by_id call can return false, so normalise to null.
 870          $file = get_file_storage()->get_file_by_id($this->file);
 871          return ($file) ? $file : null;
 872      }
 873  }