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  /**
  19   * Definition of a class stored_file.
  20   *
  21   * @package   core_files
  22   * @copyright 2008 Petr Skoda {@link http://skodak.org}
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  use Psr\Http\Message\StreamInterface;
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  require_once($CFG->dirroot . '/lib/filestorage/file_progress.php');
  31  require_once($CFG->dirroot . '/lib/filestorage/file_system.php');
  32  
  33  /**
  34   * Class representing local files stored in a sha1 file pool.
  35   *
  36   * Since Moodle 2.0 file contents are stored in sha1 pool and
  37   * all other file information is stored in new "files" database table.
  38   *
  39   * @package   core_files
  40   * @category  files
  41   * @copyright 2008 Petr Skoda {@link http://skodak.org}
  42   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  43   * @since     Moodle 2.0
  44   */
  45  class stored_file {
  46      /** @var file_storage file storage pool instance */
  47      private $fs;
  48      /** @var stdClass record from the files table left join files_reference table */
  49      private $file_record;
  50      /** @var repository repository plugin instance */
  51      private $repository;
  52      /** @var file_system filesystem instance */
  53      private $filesystem;
  54  
  55      /**
  56       * @var int Indicates a file handle of the type returned by fopen.
  57       */
  58      const FILE_HANDLE_FOPEN = 0;
  59  
  60      /**
  61       * @var int Indicates a file handle of the type returned by gzopen.
  62       */
  63      const FILE_HANDLE_GZOPEN = 1;
  64  
  65  
  66      /**
  67       * Constructor, this constructor should be called ONLY from the file_storage class!
  68       *
  69       * @param file_storage $fs file  storage instance
  70       * @param stdClass $file_record description of file
  71       * @param string $deprecated
  72       */
  73      public function __construct(file_storage $fs, stdClass $file_record, $deprecated = null) {
  74          global $DB, $CFG;
  75          $this->fs          = $fs;
  76          $this->file_record = clone($file_record); // prevent modifications
  77  
  78          if (!empty($file_record->repositoryid)) {
  79              require_once("$CFG->dirroot/repository/lib.php");
  80              $this->repository = repository::get_repository_by_id($file_record->repositoryid, SYSCONTEXTID);
  81              if ($this->repository->supported_returntypes() & FILE_REFERENCE != FILE_REFERENCE) {
  82                  // Repository cannot do file reference.
  83                  throw new moodle_exception('error');
  84              }
  85          } else {
  86              $this->repository = null;
  87          }
  88          // make sure all reference fields exist in file_record even when it is not a reference
  89          foreach (array('referencelastsync', 'referencefileid', 'reference', 'repositoryid') as $key) {
  90              if (empty($this->file_record->$key)) {
  91                  $this->file_record->$key = null;
  92              }
  93          }
  94  
  95          $this->filesystem = $fs->get_file_system();
  96      }
  97  
  98      /**
  99       * Magic method, called during serialization.
 100       *
 101       * @return array
 102       */
 103      public function __sleep() {
 104          // We only ever want the file_record saved, not the file_storage object.
 105          return ['file_record'];
 106      }
 107  
 108      /**
 109       * Magic method, called during unserialization.
 110       */
 111      public function __wakeup() {
 112          // Recreate our stored_file based on the file_record, and using file storage retrieved the correct way.
 113          $this->__construct(get_file_storage(), $this->file_record);
 114      }
 115  
 116      /**
 117       * Whether or not this is a external resource
 118       *
 119       * @return bool
 120       */
 121      public function is_external_file() {
 122          return !empty($this->repository);
 123      }
 124  
 125      /**
 126       * Whether or not this is a controlled link. Note that repositories cannot support FILE_REFERENCE and FILE_CONTROLLED_LINK.
 127       *
 128       * @return bool
 129       */
 130      public function is_controlled_link() {
 131          return $this->is_external_file() && $this->repository->supported_returntypes() & FILE_CONTROLLED_LINK;
 132      }
 133  
 134      /**
 135       * Update some file record fields
 136       * NOTE: Must remain protected
 137       *
 138       * @param stdClass $dataobject
 139       */
 140      protected function update($dataobject) {
 141          global $DB;
 142          $updatereferencesneeded = false;
 143          $updatemimetype = false;
 144          $keys = array_keys((array)$this->file_record);
 145          $filepreupdate = clone($this->file_record);
 146          foreach ($dataobject as $field => $value) {
 147              if (in_array($field, $keys)) {
 148                  if ($field == 'contextid' and (!is_number($value) or $value < 1)) {
 149                      throw new file_exception('storedfileproblem', 'Invalid contextid');
 150                  }
 151  
 152                  if ($field == 'component') {
 153                      $value = clean_param($value, PARAM_COMPONENT);
 154                      if (empty($value)) {
 155                          throw new file_exception('storedfileproblem', 'Invalid component');
 156                      }
 157                  }
 158  
 159                  if ($field == 'filearea') {
 160                      $value = clean_param($value, PARAM_AREA);
 161                      if (empty($value)) {
 162                          throw new file_exception('storedfileproblem', 'Invalid filearea');
 163                      }
 164                  }
 165  
 166                  if ($field == 'itemid' and (!is_number($value) or $value < 0)) {
 167                      throw new file_exception('storedfileproblem', 'Invalid itemid');
 168                  }
 169  
 170  
 171                  if ($field == 'filepath') {
 172                      $value = clean_param($value, PARAM_PATH);
 173                      if (strpos($value, '/') !== 0 or strrpos($value, '/') !== strlen($value)-1) {
 174                          // path must start and end with '/'
 175                          throw new file_exception('storedfileproblem', 'Invalid file path');
 176                      }
 177                  }
 178  
 179                  if ($field == 'filename') {
 180                      // folder has filename == '.', so we pass this
 181                      if ($value != '.') {
 182                          $value = clean_param($value, PARAM_FILE);
 183                      }
 184                      if ($value === '') {
 185                          throw new file_exception('storedfileproblem', 'Invalid file name');
 186                      }
 187                  }
 188  
 189                  if ($field === 'timecreated' or $field === 'timemodified') {
 190                      if (!is_number($value)) {
 191                          throw new file_exception('storedfileproblem', 'Invalid timestamp');
 192                      }
 193                      if ($value < 0) {
 194                          $value = 0;
 195                      }
 196                  }
 197  
 198                  if ($field === 'referencefileid') {
 199                      if (!is_null($value) and !is_number($value)) {
 200                          throw new file_exception('storedfileproblem', 'Invalid reference info');
 201                      }
 202                  }
 203  
 204                  if (($field == 'contenthash' || $field == 'filesize') && $this->file_record->$field != $value) {
 205                      $updatereferencesneeded = true;
 206                  }
 207  
 208                  if ($updatereferencesneeded || ($field === 'filename' && $this->file_record->filename != $value)) {
 209                      $updatemimetype = true;
 210                  }
 211  
 212                  // adding the field
 213                  $this->file_record->$field = $value;
 214              } else {
 215                  throw new coding_exception("Invalid field name, $field doesn't exist in file record");
 216              }
 217          }
 218          // Validate mimetype field
 219          if ($updatemimetype) {
 220              $mimetype = $this->filesystem->mimetype_from_storedfile($this);
 221              $this->file_record->mimetype = $mimetype;
 222          }
 223  
 224          $DB->update_record('files', $this->file_record);
 225          if ($updatereferencesneeded) {
 226              // Either filesize or contenthash of this file have changed. Update all files that reference to it.
 227              $this->fs->update_references_to_storedfile($this);
 228          }
 229  
 230          // Callback for file update.
 231          if (!$this->is_directory()) {
 232              if ($pluginsfunction = get_plugins_with_function('after_file_updated')) {
 233                  foreach ($pluginsfunction as $plugintype => $plugins) {
 234                      foreach ($plugins as $pluginfunction) {
 235                          $pluginfunction($this->file_record, $filepreupdate);
 236                      }
 237                  }
 238              }
 239          }
 240      }
 241  
 242      /**
 243       * Rename filename
 244       *
 245       * @param string $filepath file path
 246       * @param string $filename file name
 247       */
 248      public function rename($filepath, $filename) {
 249          if ($this->fs->file_exists($this->get_contextid(), $this->get_component(), $this->get_filearea(), $this->get_itemid(), $filepath, $filename)) {
 250              $a = new stdClass();
 251              $a->contextid = $this->get_contextid();
 252              $a->component = $this->get_component();
 253              $a->filearea  = $this->get_filearea();
 254              $a->itemid    = $this->get_itemid();
 255              $a->filepath  = $filepath;
 256              $a->filename  = $filename;
 257              throw new file_exception('storedfilenotcreated', $a, 'file exists, cannot rename');
 258          }
 259          $filerecord = new stdClass;
 260          $filerecord->filepath = $filepath;
 261          $filerecord->filename = $filename;
 262          // populate the pathname hash
 263          $filerecord->pathnamehash = $this->fs->get_pathname_hash($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath, $filename);
 264          $this->update($filerecord);
 265      }
 266  
 267      /**
 268       * Function stored_file::replace_content_with() is deprecated. Please use stored_file::replace_file_with()
 269       *
 270       * @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
 271       * @see stored_file::replace_file_with()
 272       */
 273      public function replace_content_with(stored_file $storedfile) {
 274          throw new coding_exception('Function stored_file::replace_content_with() can not be used any more . ' .
 275              'Please use stored_file::replace_file_with()');
 276      }
 277  
 278      /**
 279       * Replaces the fields that might have changed when file was overriden in filepicker:
 280       * reference, contenthash, filesize, userid
 281       *
 282       * Note that field 'source' must be updated separately because
 283       * it has different format for draft and non-draft areas and
 284       * this function will usually be used to replace non-draft area
 285       * file with draft area file.
 286       *
 287       * @param stored_file $newfile
 288       * @throws coding_exception
 289       */
 290      public function replace_file_with(stored_file $newfile) {
 291          if ($newfile->get_referencefileid() &&
 292                  $this->fs->get_references_count_by_storedfile($this)) {
 293              // The new file is a reference.
 294              // The current file has other local files referencing to it.
 295              // Double reference is not allowed.
 296              throw new moodle_exception('errordoublereference', 'repository');
 297          }
 298  
 299          $filerecord = new stdClass;
 300          if ($this->filesystem->is_file_readable_remotely_by_storedfile($newfile)) {
 301              $contenthash = $newfile->get_contenthash();
 302              $filerecord->contenthash = $contenthash;
 303          } else {
 304              throw new file_exception('storedfileproblem', 'Invalid contenthash, content must be already in filepool', $contenthash);
 305          }
 306          $filerecord->filesize = $newfile->get_filesize();
 307          $filerecord->referencefileid = $newfile->get_referencefileid();
 308          $filerecord->userid = $newfile->get_userid();
 309          $oldcontenthash = $this->get_contenthash();
 310          $this->update($filerecord);
 311          $this->filesystem->remove_file($oldcontenthash);
 312      }
 313  
 314      /**
 315       * Unlink the stored file from the referenced file
 316       *
 317       * This methods destroys the link to the record in files_reference table. This effectively
 318       * turns the stored file from being an alias to a plain copy. However, the caller has
 319       * to make sure that the actual file's content has beed synced prior to calling this method.
 320       */
 321      public function delete_reference() {
 322          global $DB;
 323  
 324          if (!$this->is_external_file()) {
 325              throw new coding_exception('An attempt to unlink a non-reference file.');
 326          }
 327  
 328          $transaction = $DB->start_delegated_transaction();
 329  
 330          // Are we the only one referring to the original file? If so, delete the
 331          // referenced file record. Note we do not use file_storage::search_references_count()
 332          // here because we want to count draft files too and we are at a bit lower access level here.
 333          $countlinks = $DB->count_records('files',
 334              array('referencefileid' => $this->file_record->referencefileid));
 335          if ($countlinks == 1) {
 336              $DB->delete_records('files_reference', array('id' => $this->file_record->referencefileid));
 337          }
 338  
 339          // Update the underlying record in the database.
 340          $update = new stdClass();
 341          $update->referencefileid = null;
 342          $this->update($update);
 343  
 344          $transaction->allow_commit();
 345  
 346          // Update our properties and the record in the memory.
 347          $this->repository = null;
 348          $this->file_record->repositoryid = null;
 349          $this->file_record->reference = null;
 350          $this->file_record->referencefileid = null;
 351          $this->file_record->referencelastsync = null;
 352      }
 353  
 354      /**
 355       * Is this a directory?
 356       *
 357       * Directories are only emulated, internally they are stored as empty
 358       * files with a "." instead of name - this means empty directory contains
 359       * exactly one empty file with name dot.
 360       *
 361       * @return bool true means directory, false means file
 362       */
 363      public function is_directory() {
 364          return ($this->file_record->filename === '.');
 365      }
 366  
 367      /**
 368       * Delete file from files table.
 369       *
 370       * The content of files stored in sha1 pool is reclaimed
 371       * later - the occupied disk space is reclaimed much later.
 372       *
 373       * @return bool always true or exception if error occurred
 374       */
 375      public function delete() {
 376          global $DB;
 377  
 378          if ($this->is_directory()) {
 379              // Directories can not be referenced, just delete the record.
 380              $DB->delete_records('files', array('id'=>$this->file_record->id));
 381  
 382          } else {
 383              $transaction = $DB->start_delegated_transaction();
 384  
 385              // If there are other files referring to this file, convert them to copies.
 386              if ($files = $this->fs->get_references_by_storedfile($this)) {
 387                  foreach ($files as $file) {
 388                      $this->fs->import_external_file($file);
 389                  }
 390              }
 391  
 392              // If this file is a reference (alias) to another file, unlink it first.
 393              if ($this->is_external_file()) {
 394                  $this->delete_reference();
 395              }
 396  
 397              // Now delete the file record.
 398              $DB->delete_records('files', array('id'=>$this->file_record->id));
 399  
 400              $transaction->allow_commit();
 401  
 402              if (!$this->is_directory()) {
 403                  // Callback for file deletion.
 404                  if ($pluginsfunction = get_plugins_with_function('after_file_deleted')) {
 405                      foreach ($pluginsfunction as $plugintype => $plugins) {
 406                          foreach ($plugins as $pluginfunction) {
 407                              $pluginfunction($this->file_record);
 408                          }
 409                      }
 410                  }
 411              }
 412          }
 413  
 414          // Move pool file to trash if content not needed any more.
 415          $this->filesystem->remove_file($this->file_record->contenthash);
 416          return true; // BC only
 417      }
 418  
 419      /**
 420      * adds this file path to a curl request (POST only)
 421      *
 422      * @param curl $curlrequest the curl request object
 423      * @param string $key what key to use in the POST request
 424      * @return void
 425      */
 426      public function add_to_curl_request(&$curlrequest, $key) {
 427          return $this->filesystem->add_to_curl_request($this, $curlrequest, $key);
 428      }
 429  
 430      /**
 431       * Returns file handle - read only mode, no writing allowed into pool files!
 432       *
 433       * When you want to modify a file, create a new file and delete the old one.
 434       *
 435       * @param int $type Type of file handle (FILE_HANDLE_xx constant)
 436       * @return resource file handle
 437       */
 438      public function get_content_file_handle($type = self::FILE_HANDLE_FOPEN) {
 439          return $this->filesystem->get_content_file_handle($this, $type);
 440      }
 441  
 442      /**
 443       * Get a read-only PSR-7 stream for this file.
 444       *
 445       * Note: This stream is read-only. If you want to modify the file, create a new file and delete the old one.
 446       * The File API creates immutable files.
 447       *
 448       * @return StreamInterface
 449       */
 450      public function get_psr_stream(): StreamInterface {
 451          return $this->filesystem->get_psr_stream($this);
 452      }
 453  
 454      /**
 455       * Dumps file content to page.
 456       */
 457      public function readfile() {
 458          return $this->filesystem->readfile($this);
 459      }
 460  
 461      /**
 462       * Returns file content as string.
 463       *
 464       * @return string content
 465       */
 466      public function get_content() {
 467          return $this->filesystem->get_content($this);
 468      }
 469  
 470      /**
 471       * Copy content of file to given pathname.
 472       *
 473       * @param string $pathname real path to the new file
 474       * @return bool success
 475       */
 476      public function copy_content_to($pathname) {
 477          return $this->filesystem->copy_content_from_storedfile($this, $pathname);
 478      }
 479  
 480      /**
 481       * Copy content of file to temporary folder and returns file path
 482       *
 483       * @param string $dir name of the temporary directory
 484       * @param string $fileprefix prefix of temporary file.
 485       * @return string|bool path of temporary file or false.
 486       */
 487      public function copy_content_to_temp($dir = 'files', $fileprefix = 'tempup_') {
 488          $tempfile = false;
 489          if (!$dir = make_temp_directory($dir)) {
 490              return false;
 491          }
 492          if (!$tempfile = tempnam($dir, $fileprefix)) {
 493              return false;
 494          }
 495          if (!$this->copy_content_to($tempfile)) {
 496              // something went wrong
 497              @unlink($tempfile);
 498              return false;
 499          }
 500          return $tempfile;
 501      }
 502  
 503      /**
 504       * List contents of archive.
 505       *
 506       * @param file_packer $packer file packer instance
 507       * @return array of file infos
 508       */
 509      public function list_files(file_packer $packer) {
 510          return $this->filesystem->list_files($this, $packer);
 511      }
 512  
 513      /**
 514       * Returns the total size (in bytes) of the contents of an archive.
 515       *
 516       * @param file_packer $packer file packer instance
 517       * @return int|null total size in bytes
 518       */
 519      public function get_total_content_size(file_packer $packer): ?int {
 520          // Fetch the contents of the archive.
 521          $files = $this->list_files($packer);
 522  
 523          // Early return if the value of $files is not of type array.
 524          // This can happen when the utility class is unable to open or read the contents of the archive.
 525          if (!is_array($files)) {
 526              return null;
 527          }
 528  
 529          return array_reduce($files, function ($contentsize, $file) {
 530              return $contentsize + $file->size;
 531          }, 0);
 532      }
 533  
 534      /**
 535       * Extract file to given file path (real OS filesystem), existing files are overwritten.
 536       *
 537       * @param file_packer $packer file packer instance
 538       * @param string $pathname target directory
 539       * @param file_progress $progress Progress indicator callback or null if not required
 540       * @return array|bool list of processed files; false if error
 541       */
 542      public function extract_to_pathname(file_packer $packer, $pathname,
 543              file_progress $progress = null) {
 544          return $this->filesystem->extract_to_pathname($this, $packer, $pathname, $progress);
 545      }
 546  
 547      /**
 548       * Extract file to given file path (real OS filesystem), existing files are overwritten.
 549       *
 550       * @param file_packer $packer file packer instance
 551       * @param int $contextid context ID
 552       * @param string $component component
 553       * @param string $filearea file area
 554       * @param int $itemid item ID
 555       * @param string $pathbase path base
 556       * @param int $userid user ID
 557       * @param file_progress $progress Progress indicator callback or null if not required
 558       * @return array|bool list of processed files; false if error
 559       */
 560      public function extract_to_storage(file_packer $packer, $contextid,
 561              $component, $filearea, $itemid, $pathbase, $userid = null, file_progress $progress = null) {
 562  
 563          return $this->filesystem->extract_to_storage($this, $packer, $contextid, $component, $filearea,
 564                  $itemid, $pathbase, $userid, $progress);
 565      }
 566  
 567      /**
 568       * Add file/directory into archive.
 569       *
 570       * @param file_archive $filearch file archive instance
 571       * @param string $archivepath pathname in archive
 572       * @return bool success
 573       */
 574      public function archive_file(file_archive $filearch, $archivepath) {
 575          if ($this->repository) {
 576              $this->sync_external_file();
 577              if ($this->compare_to_string('')) {
 578                  // This file is not stored locally - attempt to retrieve it from the repository.
 579                  // This may happen if the repository deliberately does not fetch files, or if there is a failure with the sync.
 580                  $fileinfo = $this->repository->get_file($this->get_reference());
 581                  if (isset($fileinfo['path'])) {
 582                      return $filearch->add_file_from_pathname($archivepath, $fileinfo['path']);
 583                  }
 584              }
 585          }
 586  
 587          return $this->filesystem->add_storedfile_to_archive($this, $filearch, $archivepath);
 588      }
 589  
 590      /**
 591       * Returns information about image,
 592       * information is determined from the file content
 593       *
 594       * @return mixed array with width, height and mimetype; false if not an image
 595       */
 596      public function get_imageinfo() {
 597          return $this->filesystem->get_imageinfo($this);
 598      }
 599  
 600      /**
 601       * Verifies the file is a valid web image - gif, png and jpeg only.
 602       *
 603       * It should be ok to serve this image from server without any other security workarounds.
 604       *
 605       * @return bool true if file ok
 606       */
 607      public function is_valid_image() {
 608          $mimetype = $this->get_mimetype();
 609          if (!file_mimetype_in_typegroup($mimetype, 'web_image')) {
 610              return false;
 611          }
 612          if (!$info = $this->get_imageinfo()) {
 613              return false;
 614          }
 615          if ($info['mimetype'] !== $mimetype) {
 616              return false;
 617          }
 618          // ok, GD likes this image
 619          return true;
 620      }
 621  
 622      /**
 623       * Returns parent directory, creates missing parents if needed.
 624       *
 625       * @return stored_file
 626       */
 627      public function get_parent_directory() {
 628          if ($this->file_record->filepath === '/' and $this->file_record->filename === '.') {
 629              //root dir does not have parent
 630              return null;
 631          }
 632  
 633          if ($this->file_record->filename !== '.') {
 634              return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $this->file_record->filepath);
 635          }
 636  
 637          $filepath = $this->file_record->filepath;
 638          $filepath = trim($filepath, '/');
 639          $dirs = explode('/', $filepath);
 640          array_pop($dirs);
 641          $filepath = implode('/', $dirs);
 642          $filepath = ($filepath === '') ? '/' : "/$filepath/";
 643  
 644          return $this->fs->create_directory($this->file_record->contextid, $this->file_record->component, $this->file_record->filearea, $this->file_record->itemid, $filepath);
 645      }
 646  
 647      /**
 648       * Set synchronised content from file.
 649       *
 650       * @param string $path Path to the file.
 651       */
 652      public function set_synchronised_content_from_file($path) {
 653          $this->fs->synchronise_stored_file_from_file($this, $path, $this->file_record);
 654      }
 655  
 656      /**
 657       * Set synchronised content from content.
 658       *
 659       * @param string $content File content.
 660       */
 661      public function set_synchronised_content_from_string($content) {
 662          $this->fs->synchronise_stored_file_from_string($this, $content, $this->file_record);
 663      }
 664  
 665      /**
 666       * Synchronize file if it is a reference and needs synchronizing
 667       *
 668       * Updates contenthash and filesize
 669       */
 670      public function sync_external_file() {
 671          if (!empty($this->repository)) {
 672              $this->repository->sync_reference($this);
 673          }
 674      }
 675  
 676      /**
 677       * Returns context id of the file
 678       *
 679       * @return int context id
 680       */
 681      public function get_contextid() {
 682          return $this->file_record->contextid;
 683      }
 684  
 685      /**
 686       * Returns component name - this is the owner of the areas,
 687       * nothing else is allowed to read or modify the files directly!!
 688       *
 689       * @return string
 690       */
 691      public function get_component() {
 692          return $this->file_record->component;
 693      }
 694  
 695      /**
 696       * Returns file area name, this divides files of one component into groups with different access control.
 697       * All files in one area have the same access control.
 698       *
 699       * @return string
 700       */
 701      public function get_filearea() {
 702          return $this->file_record->filearea;
 703      }
 704  
 705      /**
 706       * Returns returns item id of file.
 707       *
 708       * @return int
 709       */
 710      public function get_itemid() {
 711          return $this->file_record->itemid;
 712      }
 713  
 714      /**
 715       * Returns file path - starts and ends with /, \ are not allowed.
 716       *
 717       * @return string
 718       */
 719      public function get_filepath() {
 720          return $this->file_record->filepath;
 721      }
 722  
 723      /**
 724       * Returns file name or '.' in case of directories.
 725       *
 726       * @return string
 727       */
 728      public function get_filename() {
 729          return $this->file_record->filename;
 730      }
 731  
 732      /**
 733       * Returns id of user who created the file.
 734       *
 735       * @return int
 736       */
 737      public function get_userid() {
 738          return $this->file_record->userid;
 739      }
 740  
 741      /**
 742       * Returns the size of file in bytes.
 743       *
 744       * @return int bytes
 745       */
 746      public function get_filesize() {
 747          $this->sync_external_file();
 748          return $this->file_record->filesize;
 749      }
 750  
 751       /**
 752       * Function stored_file::set_filesize() is deprecated. Please use stored_file::replace_file_with
 753       *
 754       * @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
 755       * @see stored_file::replace_file_with()
 756       */
 757      public function set_filesize($filesize) {
 758          throw new coding_exception('Function stored_file::set_filesize() can not be used any more. ' .
 759              'Please use stored_file::replace_file_with()');
 760      }
 761  
 762      /**
 763       * Returns mime type of file.
 764       *
 765       * @return string
 766       */
 767      public function get_mimetype() {
 768          return $this->file_record->mimetype;
 769      }
 770  
 771      /**
 772       * Returns unix timestamp of file creation date.
 773       *
 774       * @return int
 775       */
 776      public function get_timecreated() {
 777          return $this->file_record->timecreated;
 778      }
 779  
 780      /**
 781       * Returns unix timestamp of last file modification.
 782       *
 783       * @return int
 784       */
 785      public function get_timemodified() {
 786          $this->sync_external_file();
 787          return $this->file_record->timemodified;
 788      }
 789  
 790      /**
 791       * set timemodified
 792       *
 793       * @param int $timemodified
 794       */
 795      public function set_timemodified($timemodified) {
 796          $filerecord = new stdClass;
 797          $filerecord->timemodified = $timemodified;
 798          $this->update($filerecord);
 799      }
 800  
 801      /**
 802       * Returns file status flag.
 803       *
 804       * @return int 0 means file OK, anything else is a problem and file can not be used
 805       */
 806      public function get_status() {
 807          return $this->file_record->status;
 808      }
 809  
 810      /**
 811       * Returns file id.
 812       *
 813       * @return int
 814       */
 815      public function get_id() {
 816          return $this->file_record->id;
 817      }
 818  
 819      /**
 820       * Returns sha1 hash of file content.
 821       *
 822       * @return string
 823       */
 824      public function get_contenthash() {
 825          $this->sync_external_file();
 826          return $this->file_record->contenthash;
 827      }
 828  
 829      /**
 830       * Returns sha1 hash of all file path components sha1("contextid/component/filearea/itemid/dir/dir/filename.ext").
 831       *
 832       * @return string
 833       */
 834      public function get_pathnamehash() {
 835          return $this->file_record->pathnamehash;
 836      }
 837  
 838      /**
 839       * Returns the license type of the file, it is a short name referred from license table.
 840       *
 841       * @return string
 842       */
 843      public function get_license() {
 844          return $this->file_record->license;
 845      }
 846  
 847      /**
 848       * Set license
 849       *
 850       * @param string $license license
 851       */
 852      public function set_license($license) {
 853          $filerecord = new stdClass;
 854          $filerecord->license = $license;
 855          $this->update($filerecord);
 856      }
 857  
 858      /**
 859       * Returns the author name of the file.
 860       *
 861       * @return string
 862       */
 863      public function get_author() {
 864          return $this->file_record->author;
 865      }
 866  
 867      /**
 868       * Set author
 869       *
 870       * @param string $author
 871       */
 872      public function set_author($author) {
 873          $filerecord = new stdClass;
 874          $filerecord->author = $author;
 875          $this->update($filerecord);
 876      }
 877  
 878      /**
 879       * Returns the source of the file, usually it is a url.
 880       *
 881       * @return string
 882       */
 883      public function get_source() {
 884          return $this->file_record->source;
 885      }
 886  
 887      /**
 888       * Set license
 889       *
 890       * @param string $license license
 891       */
 892      public function set_source($source) {
 893          $filerecord = new stdClass;
 894          $filerecord->source = $source;
 895          $this->update($filerecord);
 896      }
 897  
 898  
 899      /**
 900       * Returns the sort order of file
 901       *
 902       * @return int
 903       */
 904      public function get_sortorder() {
 905          return $this->file_record->sortorder;
 906      }
 907  
 908      /**
 909       * Set file sort order
 910       *
 911       * @param int $sortorder
 912       * @return int
 913       */
 914      public function set_sortorder($sortorder) {
 915          $oldorder = $this->file_record->sortorder;
 916          $filerecord = new stdClass;
 917          $filerecord->sortorder = $sortorder;
 918          $this->update($filerecord);
 919          if (!$this->is_directory()) {
 920              // Callback for file sort order change.
 921              if ($pluginsfunction = get_plugins_with_function('after_file_sorted')) {
 922                  foreach ($pluginsfunction as $plugintype => $plugins) {
 923                      foreach ($plugins as $pluginfunction) {
 924                          $pluginfunction($this->file_record, $oldorder, $sortorder);
 925                      }
 926                  }
 927              }
 928          }
 929      }
 930  
 931      /**
 932       * Returns repository id
 933       *
 934       * @return int|null
 935       */
 936      public function get_repository_id() {
 937          if (!empty($this->repository)) {
 938              return $this->repository->id;
 939          } else {
 940              return null;
 941          }
 942      }
 943  
 944      /**
 945       * Returns repository type.
 946       *
 947       * @return mixed str|null the repository type or null if is not an external file
 948       * @since  Moodle 3.3
 949       */
 950      public function get_repository_type() {
 951  
 952          if (!empty($this->repository)) {
 953              return $this->repository->get_typename();
 954          } else {
 955              return null;
 956          }
 957      }
 958  
 959  
 960      /**
 961       * get reference file id
 962       * @return int
 963       */
 964      public function get_referencefileid() {
 965          return $this->file_record->referencefileid;
 966      }
 967  
 968      /**
 969       * Get reference last sync time
 970       * @return int
 971       */
 972      public function get_referencelastsync() {
 973          return $this->file_record->referencelastsync;
 974      }
 975  
 976      /**
 977       * Function stored_file::get_referencelifetime() is deprecated as reference
 978       * life time is no longer stored in DB or returned by repository. Each
 979       * repository should decide by itself when to synchronise the references.
 980       *
 981       * @deprecated since Moodle 2.6 MDL-42016 - please do not use this function any more.
 982       * @see repository::sync_reference()
 983       */
 984      public function get_referencelifetime() {
 985          throw new coding_exception('Function stored_file::get_referencelifetime() can not be used any more. ' .
 986              'See repository::sync_reference().');
 987      }
 988      /**
 989       * Returns file reference
 990       *
 991       * @return string
 992       */
 993      public function get_reference() {
 994          return $this->file_record->reference;
 995      }
 996  
 997      /**
 998       * Get human readable file reference information
 999       *
1000       * @return string
1001       */
1002      public function get_reference_details() {
1003          return $this->repository->get_reference_details($this->get_reference(), $this->get_status());
1004      }
1005  
1006      /**
1007       * Called after reference-file has been synchronized with the repository
1008       *
1009       * We update contenthash, filesize and status in files table if changed
1010       * and we always update lastsync in files_reference table
1011       *
1012       * @param null|string $contenthash if set to null contenthash is not changed
1013       * @param int $filesize new size of the file
1014       * @param int $status new status of the file (0 means OK, 666 - source missing)
1015       * @param int $timemodified last time modified of the source, if known
1016       */
1017      public function set_synchronized($contenthash, $filesize, $status = 0, $timemodified = null) {
1018          if (!$this->is_external_file()) {
1019              return;
1020          }
1021          $now = time();
1022          if ($contenthash === null) {
1023              $contenthash = $this->file_record->contenthash;
1024          }
1025          if ($contenthash != $this->file_record->contenthash) {
1026              $oldcontenthash = $this->file_record->contenthash;
1027          }
1028          // this will update all entries in {files} that have the same filereference id
1029          $this->fs->update_references($this->file_record->referencefileid, $now, null, $contenthash, $filesize, $status, $timemodified);
1030          // we don't need to call update() for this object, just set the values of changed fields
1031          $this->file_record->contenthash = $contenthash;
1032          $this->file_record->filesize = $filesize;
1033          $this->file_record->status = $status;
1034          $this->file_record->referencelastsync = $now;
1035          if ($timemodified) {
1036              $this->file_record->timemodified = $timemodified;
1037          }
1038          if (isset($oldcontenthash)) {
1039              $this->filesystem->remove_file($oldcontenthash);
1040          }
1041      }
1042  
1043      /**
1044       * Sets the error status for a file that could not be synchronised
1045       */
1046      public function set_missingsource() {
1047          $this->set_synchronized($this->file_record->contenthash, $this->file_record->filesize, 666);
1048      }
1049  
1050      /**
1051       * Send file references
1052       *
1053       * @param int $lifetime Number of seconds before the file should expire from caches (default 24 hours)
1054       * @param int $filter 0 (default)=no filtering, 1=all files, 2=html files only
1055       * @param bool $forcedownload If true (default false), forces download of file rather than view in browser/plugin
1056       * @param array $options additional options affecting the file serving
1057       */
1058      public function send_file($lifetime, $filter, $forcedownload, $options) {
1059          $this->repository->send_file($this, $lifetime, $filter, $forcedownload, $options);
1060      }
1061  
1062      /**
1063       * Imports the contents of an external file into moodle filepool.
1064       *
1065       * @throws moodle_exception if file could not be downloaded or is too big
1066       * @param int $maxbytes throw an exception if file size is bigger than $maxbytes (0 means no limit)
1067       */
1068      public function import_external_file_contents($maxbytes = 0) {
1069          if ($this->repository) {
1070              $this->repository->import_external_file_contents($this, $maxbytes);
1071          }
1072      }
1073  
1074      /**
1075       * Gets a file relative to this file in the repository and sends it to the browser.
1076       * Checks the function repository::supports_relative_file() to make sure it can be used.
1077       *
1078       * @param string $relativepath the relative path to the file we are trying to access
1079       */
1080      public function send_relative_file($relativepath) {
1081          if ($this->repository && $this->repository->supports_relative_file()) {
1082              $relativepath = clean_param($relativepath, PARAM_PATH);
1083              $this->repository->send_relative_file($this, $relativepath);
1084          } else {
1085              send_file_not_found();
1086          }
1087      }
1088  
1089      /**
1090       * Generates a thumbnail for this stored_file.
1091       *
1092       * If the GD library has at least version 2 and PNG support is available, the returned data
1093       * is the content of a transparent PNG file containing the thumbnail. Otherwise, the function
1094       * returns contents of a JPEG file with black background containing the thumbnail.
1095       *
1096       * @param   int $width the width of the requested thumbnail
1097       * @param   int $height the height of the requested thumbnail
1098       * @return  string|bool false if a problem occurs, the thumbnail image data otherwise
1099       */
1100      public function generate_image_thumbnail($width, $height) {
1101          global $CFG;
1102          require_once($CFG->libdir . '/gdlib.php');
1103  
1104          if (empty($width) or empty($height)) {
1105              return false;
1106          }
1107  
1108          $content = $this->get_content();
1109  
1110          // Fetch the image information for this image.
1111          $imageinfo = @getimagesizefromstring($content);
1112          if (empty($imageinfo)) {
1113              return false;
1114          }
1115  
1116          // Create a new image from the file.
1117          $original = @imagecreatefromstring($content);
1118  
1119          // Generate the thumbnail.
1120          return generate_image_thumbnail_from_image($original, $imageinfo, $width, $height);
1121      }
1122  
1123      /**
1124       * Generate a resized image for this stored_file.
1125       *
1126       * @param int|null $width The desired width, or null to only use the height.
1127       * @param int|null $height The desired height, or null to only use the width.
1128       * @return string|false False when a problem occurs, else the image data.
1129       */
1130      public function resize_image($width, $height) {
1131          global $CFG;
1132          require_once($CFG->libdir . '/gdlib.php');
1133  
1134          $content = $this->get_content();
1135  
1136          // Fetch the image information for this image.
1137          $imageinfo = @getimagesizefromstring($content);
1138          if (empty($imageinfo)) {
1139              return false;
1140          }
1141  
1142          // Create a new image from the file.
1143          $original = @imagecreatefromstring($content);
1144          if (empty($original)) {
1145              return false;
1146          }
1147  
1148          // Generate the resized image.
1149          return resize_image_from_image($original, $imageinfo, $width, $height);
1150      }
1151  
1152      /**
1153       * Check whether the supplied file is the same as this file.
1154       *
1155       * @param   string $path The path to the file on disk
1156       * @return  boolean
1157       */
1158      public function compare_to_path($path) {
1159          return $this->get_contenthash() === file_storage::hash_from_path($path);
1160      }
1161  
1162      /**
1163       * Check whether the supplied content is the same as this file.
1164       *
1165       * @param   string $content The file content
1166       * @return  boolean
1167       */
1168      public function compare_to_string($content) {
1169          return $this->get_contenthash() === file_storage::hash_from_string($content);
1170      }
1171  
1172      /**
1173       * Generate a rotated image for this stored_file based on exif information.
1174       *
1175       * @return array|false False when a problem occurs, else the image data and image size.
1176       * @since Moodle 3.8
1177       */
1178      public function rotate_image() {
1179          $content = $this->get_content();
1180          $mimetype = $this->get_mimetype();
1181  
1182          if ($mimetype === "image/jpeg" && function_exists("exif_read_data")) {
1183              $exif = @exif_read_data("data://image/jpeg;base64," . base64_encode($content));
1184              if (isset($exif['ExifImageWidth']) && isset($exif['ExifImageLength']) && isset($exif['Orientation'])) {
1185                  $rotation = [
1186                      3 => -180,
1187                      6 => -90,
1188                      8 => -270,
1189                  ];
1190                  $orientation = $exif['Orientation'];
1191                  if ($orientation !== 1) {
1192                      $source = @imagecreatefromstring($content);
1193                      $data = @imagerotate($source, $rotation[$orientation], 0);
1194                      if (!empty($data)) {
1195                          if ($orientation == 1 || $orientation == 3) {
1196                              $size = [
1197                                  'width' => $exif["ExifImageWidth"],
1198                                  'height' => $exif["ExifImageLength"],
1199                              ];
1200                          } else {
1201                              $size = [
1202                                  'height' => $exif["ExifImageWidth"],
1203                                  'width' => $exif["ExifImageLength"],
1204                              ];
1205                          }
1206                          imagedestroy($source);
1207                          return [$data, $size];
1208                      }
1209                  }
1210              }
1211          }
1212          return [false, false];
1213      }
1214  }