Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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