Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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