Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [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              return true;
 199          }
 200          $a = (object)array('property' => $field, 'class' => get_class($this));
 201          throw new portfolio_export_exception($this->get('exporter'), 'invalidproperty', 'portfolio', $this->get_return_url(), $a);
 202      }
 203  
 204      /**
 205       * Stores the config generated at export time.
 206       * Subclasses can retrieve values using
 207       * @see get_export_config
 208       *
 209       * @param array $config formdata
 210       */
 211      public final function set_export_config($config) {
 212          $allowed = array_merge(
 213              array('wait', 'hidewait', 'format', 'hideformat'),
 214              $this->get_allowed_export_config()
 215          );
 216          foreach ($config as $key => $value) {
 217              if (!in_array($key, $allowed)) {
 218                  $a = (object)array('property' => $key, 'class' => get_class($this));
 219                  throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
 220              }
 221              $this->exportconfig[$key] = $value;
 222          }
 223      }
 224  
 225      /**
 226       * Returns a particular export config value.
 227       * Subclasses shouldn't need to override this
 228       *
 229       * @param string $key the config item to fetch
 230       * @return null|mixed of export configuration
 231       */
 232      public final function get_export_config($key) {
 233          $allowed = array_merge(
 234              array('wait', 'hidewait', 'format', 'hideformat'),
 235              $this->get_allowed_export_config()
 236          );
 237          if (!in_array($key, $allowed)) {
 238              $a = (object)array('property' => $key, 'class' => get_class($this));
 239              throw new portfolio_export_exception($this->get('exporter'), 'invalidexportproperty', 'portfolio', $this->get_return_url(), $a);
 240          }
 241          if (!array_key_exists($key, $this->exportconfig)) {
 242              return null;
 243          }
 244          return $this->exportconfig[$key];
 245      }
 246  
 247      /**
 248       * Similar to the other allowed_config functions
 249       * if you need export config, you must provide
 250       * a list of what the fields are.
 251       * Even if you want to store stuff during export
 252       * without displaying a form to the user,
 253       * you can use this.
 254       *
 255       * @return array array of allowed keys
 256       */
 257      public function get_allowed_export_config() {
 258          return array();
 259      }
 260  
 261      /**
 262       * After the user submits their config,
 263       * they're given a confirm screen
 264       * summarising what they've chosen.
 265       * This function should return a table of nice strings => values
 266       * of what they've chosen
 267       * to be displayed in a table.
 268       *
 269       * @return bool
 270       */
 271      public function get_export_summary() {
 272          return false;
 273      }
 274  
 275      /**
 276       * Called before the portfolio plugin gets control.
 277       * This function should copy all the files it wants to
 278       * the temporary directory, using copy_existing_file
 279       * or write_new_file
 280       *
 281       * @see copy_existing_file()
 282       * @see write_new_file()
 283       */
 284      public abstract function prepare_package();
 285  
 286      /**
 287       * Helper function to copy files into the temp area
 288       * for single or multi file exports.
 289       *
 290       * @return stored_file|bool
 291       */
 292      public function prepare_package_file() {
 293          if (empty($this->singlefile) && empty($this->multifiles)) {
 294              throw new portfolio_caller_exception('invalidpreparepackagefile', 'portfolio', $this->get_return_url());
 295          }
 296          if ($this->singlefile) {
 297              return $this->exporter->copy_existing_file($this->singlefile);
 298          }
 299          foreach ($this->multifiles as $file) {
 300              $this->exporter->copy_existing_file($file);
 301          }
 302      }
 303  
 304      /**
 305       * Array of formats this caller supports.
 306       *
 307       * @return array list of formats
 308       */
 309      public final function supported_formats() {
 310          $basic = $this->base_supported_formats();
 311          if (empty($this->supportedformats)) {
 312              $specific = array();
 313          } else if (!is_array($this->supportedformats)) {
 314              debugging(get_class($this) . ' has set a non array value of member variable supported formats - working around but should be fixed in code');
 315              $specific = array($this->supportedformats);
 316          } else {
 317              $specific = $this->supportedformats;
 318          }
 319          return portfolio_most_specific_formats($specific, $basic);
 320      }
 321  
 322      /**
 323       * Base supported formats
 324       *
 325       * @throws coding_exception
 326       */
 327      public static function base_supported_formats() {
 328          throw new coding_exception('base_supported_formats() method needs to be overridden in each subclass of portfolio_caller_base');
 329      }
 330  
 331      /**
 332       * This is the "return to where you were" url
 333       */
 334      public abstract function get_return_url();
 335  
 336      /**
 337       * Callback to do whatever capability checks required
 338       * in the caller (called during the export process
 339       */
 340      public abstract function check_permissions();
 341  
 342      /**
 343       * Clean name to display to the user about this caller location
 344       */
 345      public static function display_name() {
 346          throw new coding_exception('display_name() method needs to be overridden in each subclass of portfolio_caller_base');
 347      }
 348  
 349      /**
 350       * Return a string to put at the header summarising this export.
 351       * By default, it just display the name (usually just 'assignment' or something unhelpful
 352       *
 353       * @return string
 354       */
 355      public function heading_summary() {
 356          return get_string('exportingcontentfrom', 'portfolio', $this->display_name());
 357      }
 358  
 359      /**
 360       * Load data
 361       */
 362      public abstract function load_data();
 363  
 364      /**
 365       * Set up the required files for this export.
 366       * This supports either passing files directly
 367       * or passing area arguments directly through
 368       * to the files api using file_storage::get_area_files
 369       *
 370       * @param mixed $ids one of:
 371       *                   - single file id
 372       *                   - single stored_file object
 373       *                   - array of file ids or stored_file objects
 374       *                   - null
 375       * @return void
 376       */
 377      public function set_file_and_format_data($ids=null /* ..pass arguments to area files here. */) {
 378          $args = func_get_args();
 379          array_shift($args); // shift off $ids
 380          if (empty($ids) && count($args) == 0) {
 381              return;
 382          }
 383          $files = array();
 384          $fs = get_file_storage();
 385          if (!empty($ids)) {
 386              if (is_numeric($ids) || $ids instanceof stored_file) {
 387                  $ids = array($ids);
 388              }
 389              foreach ($ids as $id) {
 390                  if ($id instanceof stored_file) {
 391                      $files[] = $id;
 392                  } else {
 393                      $files[] = $fs->get_file_by_id($id);
 394                  }
 395              }
 396          } else if (count($args) != 0) {
 397              if (count($args) < 4) {
 398                  throw new portfolio_caller_exception('invalidfileareaargs', 'portfolio');
 399              }
 400              $files = array_values(call_user_func_array(array($fs, 'get_area_files'), $args));
 401          }
 402          switch (count($files)) {
 403              case 0: return;
 404              case 1: {
 405                  $this->singlefile = $files[0];
 406                  return;
 407              }
 408              default: {
 409                  $this->multifiles = $files;
 410              }
 411          }
 412      }
 413  
 414      /**
 415       * The button-location always knows best
 416       * what the formats are... so it should be trusted.
 417       *
 418       * @todo MDL-31298 - re-analyze set_formats_from_button comment
 419       * @param array $formats array of PORTFOLIO_FORMAT_XX
 420       * @return void
 421       */
 422      public function set_formats_from_button($formats) {
 423          $base = $this->base_supported_formats();
 424          if (count($base) != count($formats)
 425                  || count($base) != count(array_intersect($base, $formats))) {
 426                  $this->supportedformats = portfolio_most_specific_formats($formats, $base);
 427                  return;
 428          }
 429          // in the case where the button hasn't actually set anything,
 430          // we need to run through again and resolve conflicts
 431          // TODO revisit this comment - it looks to me like it's lying
 432          $this->supportedformats = portfolio_most_specific_formats($formats, $formats);
 433      }
 434  
 435      /**
 436       * Adds a new format to the list of supported formats.
 437       * This functions also handles removing conflicting and less specific
 438       * formats at the same time.
 439       *
 440       * @param string $format one of PORTFOLIO_FORMAT_XX
 441       * @return void
 442       */
 443      protected function add_format($format) {
 444          if (in_array($format, $this->supportedformats)) {
 445              return;
 446          }
 447          $this->supportedformats = portfolio_most_specific_formats(array($format), $this->supportedformats);
 448      }
 449  
 450      /**
 451       * Gets mimetype
 452       *
 453       * @return string
 454       */
 455      public function get_mimetype() {
 456          if ($this->singlefile instanceof stored_file) {
 457              return $this->singlefile->get_mimetype();
 458          } else if (!empty($this->intendedmimetype)) {
 459              return $this->intendedmimetype;
 460          }
 461      }
 462  
 463      /**
 464       * Array of arguments the caller expects to be passed through to it.
 465       * This must be keyed on the argument name, and the array value is a boolean,
 466       * whether it is required, or just optional
 467       * eg array(
 468       *     id            => true,
 469       *     somethingelse => false
 470       * )
 471       */
 472      public static function expected_callbackargs() {
 473          throw new coding_exception('expected_callbackargs() method needs to be overridden in each subclass of portfolio_caller_base');
 474      }
 475  
 476  
 477      /**
 478       * Return the context for this export. used for $PAGE->set_context
 479       *
 480       * @param moodle_page $PAGE global page object
 481       */
 482      public abstract function set_context($PAGE);
 483  }
 484  
 485  /**
 486   * Base class for module callers.
 487   *
 488   * This just implements a few of the abstract functions
 489   * from portfolio_caller_base so that caller authors
 490   * don't need to.
 491   * @see also portfolio_caller_base
 492   *
 493   * @package core_portfolio
 494   * @category portfolio
 495   * @copyright 2008 Penny Leach <penny@catalyst.net.nz>
 496   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 497   */
 498  abstract class portfolio_module_caller_base extends portfolio_caller_base {
 499  
 500      /** @var object coursemodule object. set this in the constructor like $this->cm = get_coursemodule_from_instance('forum', $this->forum->id); */
 501      protected $cm;
 502  
 503      /** @var int cmid */
 504      protected $id;
 505  
 506      /** @var stdclass course object */
 507      protected $course;
 508  
 509      /**
 510       * Navigation passed to print_header.
 511       * Override this to do something more specific than the module view page
 512       * like adding more links to the breadcrumb.
 513       *
 514       * @return array
 515       */
 516      public function get_navigation() {
 517          // No extra navigation by default, link to the course module already included.
 518          $extranav = array();
 519          return array($extranav, $this->cm);
 520      }
 521  
 522      /**
 523       * The url to return to after export or on cancel.
 524       * Defaults value is set to the module 'view' page.
 525       * Override this if it's deeper inside the module.
 526       *
 527       * @return string
 528       */
 529      public function get_return_url() {
 530          global $CFG;
 531          return $CFG->wwwroot . '/mod/' . $this->cm->modname . '/view.php?id=' . $this->cm->id;
 532      }
 533  
 534      /**
 535       * Override the parent get function
 536       * to make sure when we're asked for a course,
 537       * We retrieve the object from the database as needed.
 538       *
 539       * @param string $key the name of get function
 540       * @return stdClass
 541       */
 542      public function get($key) {
 543          if ($key != 'course') {
 544              return parent::get($key);
 545          }
 546          global $DB;
 547          if (empty($this->course)) {
 548              $this->course = $DB->get_record('course', array('id' => $this->cm->course));
 549          }
 550          return $this->course;
 551      }
 552  
 553      /**
 554       * Return a string to put at the header summarising this export.
 555       * by default, this function just display the name and module instance name.
 556       * Override this to do something more specific
 557       *
 558       * @return string
 559       */
 560      public function heading_summary() {
 561          return get_string('exportingcontentfrom', 'portfolio', $this->display_name() . ': ' . $this->cm->name);
 562      }
 563  
 564      /**
 565       * Overridden to return the course module context
 566       *
 567       * @param moodle_page $PAGE global PAGE
 568       */
 569      public function set_context($PAGE) {
 570          $PAGE->set_cm($this->cm);
 571      }
 572  }