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 that are extended to create portfolio export functionality.
  19   *
  20   * For places in moodle that want to add export functionality to subclass.
  21   *
  22   * @package core_portfolio
  23   * @copyright 2008 Penny Leach <penny@catalyst.net.nz>, Martin Dougiamas
  24   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Base class for callers
  31   *
  32   * @see also portfolio_module_caller_base
  33   *
  34   * @package core_portfolio
  35   * @category portfolio
  36   * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
  37   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  abstract class portfolio_caller_base {
  40  
  41      /** @var stdClass course active during the call */
  42      protected $course;
  43  
  44      /** @var array configuration used for export. Use set_export_config and get_export_config to access */
  45      protected $exportconfig = array();
  46  
  47      /** @var stdclass user currently exporting content */
  48      protected $user;
  49  
  50      /** @var stdClass a reference to the exporter object */
  51      protected $exporter;
  52  
  53      /** @var array can be optionally overridden by subclass constructors */
  54      protected $supportedformats;
  55  
  56      /** @var stored_file single file exports configuration*/
  57      protected $singlefile;
  58  
  59      /** @var stored_file|object set this for multi file exports */
  60      protected $multifiles;
  61  
  62      /** @var string set this for generated-file exports */
  63      protected $intendedmimetype;
  64  
  65      /**
  66       * Create portfolio_caller object
  67       *
  68       * @param array $callbackargs argument properties
  69       */
  70      public function __construct($callbackargs) {
  71          $expected = call_user_func(array(get_class($this), 'expected_callbackargs'));
  72          foreach ($expected as $key => $required) {
  73              if (!array_key_exists($key, $callbackargs)) {
  74                  if ($required) {
  75                      $a = (object)array('arg' => $key, 'class' => get_class($this));
  76                      throw new portfolio_caller_exception('missingcallbackarg', 'portfolio', null, $a);
  77                  }
  78                  continue;
  79              }
  80              $this->{$key} = $callbackargs[$key];
  81          }
  82      }
  83  
  84      /**
  85       * If this caller wants any additional config items,
  86       * they should be defined here.
  87       *
  88       * @param moodleform $mform passed by reference, add elements to it.
  89       * @param portfolio_plugin_base $instance subclass of portfolio_plugin_base
  90       */
  91      public function export_config_form(&$mform, $instance) {}
  92  
  93  
  94      /**
  95       * Whether this caller wants any additional
  96       * config during export (eg options or metadata)
  97       *
  98       * @return bool
  99       */
 100      public function has_export_config() {
 101          return false;
 102      }
 103  
 104      /**
 105       * Just like the moodle form validation function,
 106       * this is passed in the data array from the form
 107       * and if a non empty array is returned, form processing will stop.
 108       *
 109       * @param array $data data from form.
 110       */
 111      public function export_config_validation($data) {}
 112  
 113      /**
 114       * How long does this reasonably expect to take..
 115       * Should we offer the user the option to wait..?
 116       * This is deliberately nonstatic so it can take filesize into account
 117       * the portfolio plugin can override this.
 118       * (so for example even if a huge file is being sent,
 119       * the download portfolio plugin doesn't care )
 120       */
 121      public abstract function expected_time();
 122  
 123      /**
 124       * Helper method to calculate expected time for multi or single file exports
 125       *
 126       * @return string file time expectation
 127       */
 128      public function expected_time_file() {
 129          if ($this->multifiles) {
 130              return portfolio_expected_time_file($this->multifiles);
 131          }
 132          else if ($this->singlefile) {
 133              return portfolio_expected_time_file($this->singlefile);
 134          }
 135          return PORTFOLIO_TIME_LOW;
 136      }
 137  
 138      /**
 139       * Function to build navigation
 140       */
 141      public abstract function get_navigation();
 142  
 143      /**
 144       * Helper function to get sha1
 145       */
 146      public abstract function get_sha1();
 147  
 148      /**
 149       * Helper function to calculate the sha1 for multi or single file exports
 150       *
 151       * @return string sha1 file exports
 152       */
 153      public function get_sha1_file() {
 154          if (empty($this->singlefile) && empty($this->multifiles)) {
 155              throw new portfolio_caller_exception('invalidsha1file', 'portfolio', $this->get_return_url());
 156          }
 157          if ($this->singlefile) {
 158              return $this->singlefile->get_contenthash();
 159          }
 160          $sha1s = array();
 161          foreach ($this->multifiles as $file) {
 162              $sha1s[] = $file->get_contenthash();
 163          }
 164          asort($sha1s);
 165          return sha1(implode('', $sha1s));
 166      }
 167  
 168      /**
 169       * Generic getter for properties belonging to this instance
 170       * <b>outside</b> the subclasses
 171       * like name, visible etc.
 172       *
 173       * @param string $field property's name
 174       * @return mixed
 175       * @throws portfolio_export_exception
 176       */
 177      public function get($field) {
 178          if (property_exists($this, $field)) {
 179              return $this->{$field};
 180          }
 181          $a = (object)array('property' => $field, 'class' => get_class($this));
 182          throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a);
 183      }
 184  
 185      /**
 186       * Generic setter for properties belonging to this instance
 187       * <b>outside</b> the subclass
 188       * like name, visible, etc.
 189       *
 190       * @param string $field property's name
 191       * @param mixed $value property's value
 192       * @return bool
 193       * @throws moodle_exception
 194       */
 195      public final function set($field, &$value) {
 196          if (property_exists($this, $field)) {
 197              $this->{$field} =& $value;
 198              $this->dirty = true;
 199              return true;
 200          }
 201          $a = (object)array('property' => $field, 'class' => get_class($this));
 202          throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a);
 203      }
 204  
 205      /**
 206       * Stores the config generated at export time.
 207       * Subclasses can retrieve values using
 208       * @see get_export_config
 209       *
 210       * @param array $config formdata
 211       */
 212      public final function set_export_config($config) {
 213          $allowed = array_merge(
 214              array('wait', 'hidewait', 'format', 'hideformat'),
 215              $this->get_allowed_export_config()
 216          );
 217          foreach ($config as $key => $value) {
 218              if (!in_array($key, $allowed)) {
 219                  $a = (object)array('property' => $key, 'class' => get_class($this));
 220                  throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
 221              }
 222              $this->exportconfig[$key] = $value;
 223          }
 224      }
 225  
 226      /**
 227       * Returns a particular export config value.
 228       * Subclasses shouldn't need to override this
 229       *
 230       * @param string $key the config item to fetch
 231       * @return null|mixed of export configuration
 232       */
 233      public final function get_export_config($key) {
 234          $allowed = array_merge(
 235              array('wait', 'hidewait', 'format', 'hideformat'),
 236              $this->get_allowed_export_config()
 237          );
 238          if (!in_array($key, $allowed)) {
 239              $a = (object)array('property' => $key, 'class' => get_class($this));
 240              throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
 241          }
 242          if (!array_key_exists($key, $this->exportconfig)) {
 243              return null;
 244          }
 245          return $this->exportconfig[$key];
 246      }
 247  
 248      /**
 249       * Similar to the other allowed_config functions
 250       * if you need export config, you must provide
 251       * a list of what the fields are.
 252       * Even if you want to store stuff during export
 253       * without displaying a form to the user,
 254       * you can use this.
 255       *
 256       * @return array array of allowed keys
 257       */
 258      public function get_allowed_export_config() {
 259          return array();
 260      }
 261  
 262      /**
 263       * After the user submits their config,
 264       * they're given a confirm screen
 265       * summarising what they've chosen.
 266       * This function should return a table of nice strings => values
 267       * of what they've chosen
 268       * to be displayed in a table.
 269       *
 270       * @return bool
 271       */
 272      public function get_export_summary() {
 273          return false;
 274      }
 275  
 276      /**
 277       * Called before the portfolio plugin gets control.
 278       * This function should copy all the files it wants to
 279       * the temporary directory, using copy_existing_file
 280       * or write_new_file
 281       *
 282       * @see copy_existing_file()
 283       * @see write_new_file()
 284       */
 285      public abstract function prepare_package();
 286  
 287      /**
 288       * Helper function to copy files into the temp area
 289       * for single or multi file exports.
 290       *
 291       * @return stored_file|bool
 292       */
 293      public function prepare_package_file() {
 294          if (empty($this->singlefile) && empty($this->multifiles)) {
 295              throw new portfolio_caller_exception('invalidpreparepackagefile', 'portfolio', $this->get_return_url());
 296          }
 297          if ($this->singlefile) {
 298              return $this->exporter->copy_existing_file($this->singlefile);
 299          }
 300          foreach ($this->multifiles as $file) {
 301              $this->exporter->copy_existing_file($file);
 302          }
 303      }
 304  
 305      /**
 306       * Array of formats this caller supports.
 307       *
 308       * @return array list of formats
 309       */
 310      public final function supported_formats() {
 311          $basic = $this->base_supported_formats();
 312          if (empty($this->supportedformats)) {
 313              $specific = array();
 314          } else if (!is_array($this->supportedformats)) {
 315              debugging(get_class($this) . ' has set a non array value of member variable supported formats - working around but should be fixed in code');
 316              $specific = array($this->supportedformats);
 317          } else {
 318              $specific = $this->supportedformats;
 319          }
 320          return portfolio_most_specific_formats($specific, $basic);
 321      }
 322  
 323      /**
 324       * Base supported formats
 325       *
 326       * @throws coding_exception
 327       */
 328      public static function base_supported_formats() {
 329          throw new coding_exception('base_supported_formats() method needs to be overridden in each subclass of portfolio_caller_base');
 330      }
 331  
 332      /**
 333       * This is the "return to where you were" url
 334       */
 335      public abstract function get_return_url();
 336  
 337      /**
 338       * Callback to do whatever capability checks required
 339       * in the caller (called during the export process
 340       */
 341      public abstract function check_permissions();
 342  
 343      /**
 344       * Clean name to display to the user about this caller location
 345       */
 346      public static function display_name() {
 347          throw new coding_exception('display_name() method needs to be overridden in each subclass of portfolio_caller_base');
 348      }
 349  
 350      /**
 351       * Return a string to put at the header summarising this export.
 352       * By default, it just display the name (usually just 'assignment' or something unhelpful
 353       *
 354       * @return string
 355       */
 356      public function heading_summary() {
 357          return get_string('exportingcontentfrom', 'portfolio', $this->display_name());
 358      }
 359  
 360      /**
 361       * Load data
 362       */
 363      public abstract function load_data();
 364  
 365      /**
 366       * Set up the required files for this export.
 367       * This supports either passing files directly
 368       * or passing area arguments directly through
 369       * to the files api using file_storage::get_area_files
 370       *
 371       * @param mixed $ids one of:
 372       *                   - single file id
 373       *                   - single stored_file object
 374       *                   - array of file ids or stored_file objects
 375       *                   - null
 376       * @return void
 377       */
 378      public function set_file_and_format_data($ids=null /* ..pass arguments to area files here. */) {
 379          $args = func_get_args();
 380          array_shift($args); // shift off $ids
 381          if (empty($ids) && count($args) == 0) {
 382              return;
 383          }
 384          $files = array();
 385          $fs = get_file_storage();
 386          if (!empty($ids)) {
 387              if (is_numeric($ids) || $ids instanceof stored_file) {
 388                  $ids = array($ids);
 389              }
 390              foreach ($ids as $id) {
 391                  if ($id instanceof stored_file) {
 392                      $files[] = $id;
 393                  } else {
 394                      $files[] = $fs->get_file_by_id($id);
 395                  }
 396              }
 397          } else if (count($args) != 0) {
 398              if (count($args) < 4) {
 399                  throw new portfolio_caller_exception('invalidfileareaargs', 'portfolio');
 400              }
 401              $files = array_values(call_user_func_array(array($fs, 'get_area_files'), $args));
 402          }
 403          switch (count($files)) {
 404              case 0: return;
 405              case 1: {
 406                  $this->singlefile = $files[0];
 407                  return;
 408              }
 409              default: {
 410                  $this->multifiles = $files;
 411              }
 412          }
 413      }
 414  
 415      /**
 416       * The button-location always knows best
 417       * what the formats are... so it should be trusted.
 418       *
 419       * @todo MDL-31298 - re-analyze set_formats_from_button comment
 420       * @param array $formats array of PORTFOLIO_FORMAT_XX
 421       * @return void
 422       */
 423      public function set_formats_from_button($formats) {
 424          $base = $this->base_supported_formats();
 425          if (count($base) != count($formats)
 426                  || count($base) != count(array_intersect($base, $formats))) {
 427                  $this->supportedformats = portfolio_most_specific_formats($formats, $base);
 428                  return;
 429          }
 430          // in the case where the button hasn't actually set anything,
 431          // we need to run through again and resolve conflicts
 432          // TODO revisit this comment - it looks to me like it's lying
 433          $this->supportedformats = portfolio_most_specific_formats($formats, $formats);
 434      }
 435  
 436      /**
 437       * Adds a new format to the list of supported formats.
 438       * This functions also handles removing conflicting and less specific
 439       * formats at the same time.
 440       *
 441       * @param string $format one of PORTFOLIO_FORMAT_XX
 442       * @return void
 443       */
 444      protected function add_format($format) {
 445          if (in_array($format, $this->supportedformats)) {
 446              return;
 447          }
 448          $this->supportedformats = portfolio_most_specific_formats(array($format), $this->supportedformats);
 449      }
 450  
 451      /**
 452       * Gets mimetype
 453       *
 454       * @return string
 455       */
 456      public function get_mimetype() {
 457          if ($this->singlefile instanceof stored_file) {
 458              return $this->singlefile->get_mimetype();
 459          } else if (!empty($this->intendedmimetype)) {
 460              return $this->intendedmimetype;
 461          }
 462      }
 463  
 464      /**
 465       * Array of arguments the caller expects to be passed through to it.
 466       * This must be keyed on the argument name, and the array value is a boolean,
 467       * whether it is required, or just optional
 468       * eg array(
 469       *     id            => true,
 470       *     somethingelse => false
 471       * )
 472       */
 473      public static function expected_callbackargs() {
 474          throw new coding_exception('expected_callbackargs() method needs to be overridden in each subclass of portfolio_caller_base');
 475      }
 476  
 477  
 478      /**
 479       * Return the context for this export. used for $PAGE->set_context
 480       *
 481       * @param moodle_page $PAGE global page object
 482       */
 483      public abstract function set_context($PAGE);
 484  }
 485  
 486  /**
 487   * Base class for module callers.
 488   *
 489   * This just implements a few of the abstract functions
 490   * from portfolio_caller_base so that caller authors
 491   * don't need to.
 492   * @see also portfolio_caller_base
 493   *
 494   * @package core_portfolio
 495   * @category portfolio
 496   * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
 497   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 498   */
 499  abstract class portfolio_module_caller_base extends portfolio_caller_base {
 500  
 501      /** @var object coursemodule object. set this in the constructor like $this->cm = get_coursemodule_from_instance('forum', $this->forum->id); */
 502      protected $cm;
 503  
 504      /** @var int cmid */
 505      protected $id;
 506  
 507      /** @var stdclass course object */
 508      protected $course;
 509  
 510      /**
 511       * Navigation passed to print_header.
 512       * Override this to do something more specific than the module view page
 513       * like adding more links to the breadcrumb.
 514       *
 515       * @return array
 516       */
 517      public function get_navigation() {
 518          // No extra navigation by default, link to the course module already included.
 519          $extranav = array();
 520          return array($extranav, $this->cm);
 521      }
 522  
 523      /**
 524       * The url to return to after export or on cancel.
 525       * Defaults value is set to the module 'view' page.
 526       * Override this if it's deeper inside the module.
 527       *
 528       * @return string
 529       */
 530      public function get_return_url() {
 531          global $CFG;
 532          return $CFG->wwwroot . '/mod/' . $this->cm->modname . '/view.php?id=' . $this->cm->id;
 533      }
 534  
 535      /**
 536       * Override the parent get function
 537       * to make sure when we're asked for a course,
 538       * We retrieve the object from the database as needed.
 539       *
 540       * @param string $key the name of get function
 541       * @return stdClass
 542       */
 543      public function get($key) {
 544          if ($key != 'course') {
 545              return parent::get($key);
 546          }
 547          global $DB;
 548          if (empty($this->course)) {
 549              $this->course = $DB->get_record('course', array('id' => $this->cm->course));
 550          }
 551          return $this->course;
 552      }
 553  
 554      /**
 555       * Return a string to put at the header summarising this export.
 556       * by default, this function just display the name and module instance name.
 557       * Override this to do something more specific
 558       *
 559       * @return string
 560       */
 561      public function heading_summary() {
 562          return get_string('exportingcontentfrom', 'portfolio', $this->display_name() . ': ' . $this->cm->name);
 563      }
 564  
 565      /**
 566       * Overridden to return the course module context
 567       *
 568       * @param moodle_page $PAGE global PAGE
 569       */
 570      public function set_context($PAGE) {
 571          $PAGE->set_cm($this->cm);
 572      }
 573  }