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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body