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