[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Part of the Joomla Framework Filesystem Package 4 * 5 * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE 7 */ 8 9 namespace Joomla\Filesystem; 10 11 use Joomla\Filesystem\Exception\FilesystemException; 12 13 /** 14 * Joomla! Stream Interface 15 * 16 * The Joomla! stream interface is designed to handle files as streams 17 * where as the legacy JFile static class treated files in a rather 18 * atomic manner. 19 * 20 * This class adheres to the stream wrapper operations: 21 * 22 * @link https://www.php.net/manual/en/function.stream-get-wrappers.php 23 * @link https://www.php.net/manual/en/intro.stream.php PHP Stream Manual 24 * @link https://www.php.net/manual/en/wrappers.php Stream Wrappers 25 * @link https://www.php.net/manual/en/filters.php Stream Filters 26 * @link https://www.php.net/manual/en/transports.php Socket Transports (used by some options, particularly HTTP proxy) 27 * @since 1.0 28 */ 29 class Stream 30 { 31 /** 32 * File Mode 33 * 34 * @var integer 35 * @since 1.0 36 */ 37 protected $filemode = 0644; 38 39 /** 40 * Directory Mode 41 * 42 * @var integer 43 * @since 1.0 44 */ 45 protected $dirmode = 0755; 46 47 /** 48 * Default Chunk Size 49 * 50 * @var integer 51 * @since 1.0 52 */ 53 protected $chunksize = 8192; 54 55 /** 56 * Filename 57 * 58 * @var string 59 * @since 1.0 60 */ 61 protected $filename; 62 63 /** 64 * Prefix of the connection for writing 65 * 66 * @var string 67 * @since 1.0 68 */ 69 protected $writeprefix; 70 71 /** 72 * Prefix of the connection for reading 73 * 74 * @var string 75 * @since 1.0 76 */ 77 protected $readprefix; 78 79 /** 80 * Read Processing method 81 * 82 * @var string gz, bz, f 83 * If a scheme is detected, fopen will be defaulted 84 * To use compression with a network stream use a filter 85 * @since 1.0 86 */ 87 protected $processingmethod = 'f'; 88 89 /** 90 * Filters applied to the current stream 91 * 92 * @var array 93 * @since 1.0 94 */ 95 protected $filters = []; 96 97 /** 98 * File Handle 99 * 100 * @var resource 101 * @since 1.0 102 */ 103 protected $fh; 104 105 /** 106 * File size 107 * 108 * @var integer 109 * @since 1.0 110 */ 111 protected $filesize; 112 113 /** 114 * Context to use when opening the connection 115 * 116 * @var string 117 * @since 1.0 118 */ 119 protected $context; 120 121 /** 122 * Context options; used to rebuild the context 123 * 124 * @var array 125 * @since 1.0 126 */ 127 protected $contextOptions; 128 129 /** 130 * The mode under which the file was opened 131 * 132 * @var string 133 * @since 1.0 134 */ 135 protected $openmode; 136 137 /** 138 * Constructor 139 * 140 * @param string $writeprefix Prefix of the stream (optional). Unlike the JPATH_*, this has a final path separator! 141 * @param string $readprefix The read prefix (optional). 142 * @param array $context The context options (optional). 143 * 144 * @since 1.0 145 */ 146 public function __construct($writeprefix = '', $readprefix = '', $context = []) 147 { 148 $this->writeprefix = $writeprefix; 149 $this->readprefix = $readprefix; 150 $this->contextOptions = $context; 151 $this->_buildContext(); 152 } 153 154 /** 155 * Destructor 156 * 157 * @since 1.0 158 */ 159 public function __destruct() 160 { 161 // Attempt to close on destruction if there is a file handle 162 if ($this->fh) 163 { 164 @$this->close(); 165 } 166 } 167 168 /** 169 * Creates a new stream object with appropriate prefix 170 * 171 * @param boolean $usePrefix Prefix the connections for writing 172 * @param string $ua UA User agent to use 173 * @param boolean $uamask User agent masking (prefix Mozilla) 174 * 175 * @return Stream 176 * 177 * @see Stream 178 * @since 1.0 179 */ 180 public static function getStream($usePrefix = true, $ua = null, $uamask = false) 181 { 182 // Setup the context; Joomla! UA and overwrite 183 $context = []; 184 185 // Set the UA for HTTP 186 $context['http']['user_agent'] = $ua ?: 'Joomla! Framework Stream'; 187 188 if ($usePrefix) 189 { 190 return new static(JPATH_ROOT . '/', JPATH_ROOT, $context); 191 } 192 193 return new static('', '', $context); 194 } 195 196 /** 197 * Generic File Operations 198 * 199 * Open a stream with some lazy loading smarts 200 * 201 * @param string $filename Filename 202 * @param string $mode Mode string to use 203 * @param boolean $useIncludePath Use the PHP include path 204 * @param resource $context Context to use when opening 205 * @param boolean $usePrefix Use a prefix to open the file 206 * @param boolean $relative Filename is a relative path (if false, strips JPATH_ROOT to make it relative) 207 * @param boolean $detectprocessingmode Detect the processing method for the file and use the appropriate function 208 * to handle output automatically 209 * 210 * @return boolean 211 * 212 * @since 1.0 213 * @throws FilesystemException 214 */ 215 public function open($filename, $mode = 'r', $useIncludePath = false, $context = null, $usePrefix = false, $relative = false, 216 $detectprocessingmode = false 217 ) 218 { 219 $filename = $this->_getFilename($filename, $mode, $usePrefix, $relative); 220 221 if (!$filename) 222 { 223 throw new FilesystemException('Filename not set'); 224 } 225 226 $this->filename = $filename; 227 $this->openmode = $mode; 228 229 $url = parse_url($filename); 230 231 if (isset($url['scheme'])) 232 { 233 $scheme = ucfirst($url['scheme']); 234 235 // Map to StringWrapper if required 236 if ($scheme === 'String') 237 { 238 $scheme = 'StringWrapper'; 239 } 240 241 // If we're dealing with a Joomla! stream, load it 242 if (Helper::isJoomlaStream($scheme)) 243 { 244 require_once __DIR__ . '/Stream/' . $scheme . '.php'; 245 } 246 247 // We have a scheme! force the method to be f 248 $this->processingmethod = 'f'; 249 } 250 elseif ($detectprocessingmode) 251 { 252 $ext = strtolower(pathinfo($this->filename, \PATHINFO_EXTENSION)); 253 254 switch ($ext) 255 { 256 case 'tgz': 257 case 'gz': 258 case 'gzip': 259 $this->processingmethod = 'gz'; 260 261 break; 262 263 case 'tbz2': 264 case 'bz2': 265 case 'bzip2': 266 $this->processingmethod = 'bz'; 267 268 break; 269 270 default: 271 $this->processingmethod = 'f'; 272 273 break; 274 } 275 } 276 277 // Capture PHP errors 278 if (PHP_VERSION_ID < 70000) 279 { 280 // @Todo Remove this path, when PHP5 support is dropped. 281 set_error_handler( 282 function () { 283 return false; 284 } 285 ); 286 @trigger_error(''); 287 restore_error_handler(); 288 } 289 else 290 { 291 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 292 error_clear_last(); 293 } 294 295 // Decide which context to use: 296 switch ($this->processingmethod) 297 { 298 // Gzip doesn't support contexts or streams 299 case 'gz': 300 $this->fh = gzopen($filename, $mode, $useIncludePath); 301 302 break; 303 304 // Bzip2 is much like gzip except it doesn't use the include path 305 case 'bz': 306 $this->fh = bzopen($filename, $mode); 307 308 break; 309 310 // Fopen can handle streams 311 case 'f': 312 default: 313 // One supplied at open; overrides everything 314 if ($context) 315 { 316 $this->fh = @fopen($filename, $mode, $useIncludePath, $context); 317 } 318 elseif ($this->context) 319 { 320 // One provided at initialisation 321 $this->fh = @fopen($filename, $mode, $useIncludePath, $this->context); 322 } 323 else 324 { 325 // No context; all defaults 326 $this->fh = @fopen($filename, $mode, $useIncludePath); 327 } 328 329 break; 330 } 331 332 if (!$this->fh) 333 { 334 $error = error_get_last(); 335 336 if ($error === null || $error['message'] === '') 337 { 338 // Error but nothing from php? Create our own 339 $error = array( 340 'message' => sprintf('Unknown error opening file %s', $filename) 341 ); 342 } 343 344 throw new FilesystemException($error['message']); 345 } 346 347 // Return the result 348 return true; 349 } 350 351 /** 352 * Attempt to close a file handle 353 * 354 * Will return false if it failed and true on success 355 * If the file is not open the system will return true, this function destroys the file handle as well 356 * 357 * @return boolean 358 * 359 * @since 1.0 360 * @throws FilesystemException 361 */ 362 public function close() 363 { 364 if (!$this->fh) 365 { 366 throw new FilesystemException('File not open'); 367 } 368 369 // Capture PHP errors 370 if (PHP_VERSION_ID < 70000) 371 { 372 // @Todo Remove this path, when PHP5 support is dropped. 373 set_error_handler( 374 function () { 375 return false; 376 } 377 ); 378 @trigger_error(''); 379 restore_error_handler(); 380 } 381 else 382 { 383 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 384 error_clear_last(); 385 } 386 387 switch ($this->processingmethod) 388 { 389 case 'gz': 390 $res = gzclose($this->fh); 391 392 break; 393 394 case 'bz': 395 $res = bzclose($this->fh); 396 397 break; 398 399 case 'f': 400 default: 401 $res = fclose($this->fh); 402 403 break; 404 } 405 406 if (!$res) 407 { 408 $error = error_get_last(); 409 410 if ($error === null || $error['message'] === '') 411 { 412 // Error but nothing from php? Create our own 413 $error = array( 414 'message' => 'Unable to close stream' 415 ); 416 } 417 418 throw new FilesystemException($error['message']); 419 } 420 421 // Reset this 422 $this->fh = null; 423 424 // If we wrote, chmod the file after it's closed 425 if ($this->openmode[0] == 'w') 426 { 427 $this->chmod(); 428 } 429 430 // Return the result 431 return true; 432 } 433 434 /** 435 * Work out if we're at the end of the file for a stream 436 * 437 * @return boolean 438 * 439 * @since 1.0 440 * @throws FilesystemException 441 */ 442 public function eof() 443 { 444 if (!$this->fh) 445 { 446 throw new FilesystemException('File not open'); 447 } 448 449 // Capture PHP errors 450 if (PHP_VERSION_ID < 70000) 451 { 452 // @Todo Remove this path, when PHP5 support is dropped. 453 set_error_handler( 454 function () { 455 return false; 456 } 457 ); 458 @trigger_error(''); 459 restore_error_handler(); 460 } 461 else 462 { 463 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 464 error_clear_last(); 465 } 466 467 switch ($this->processingmethod) 468 { 469 case 'gz': 470 $res = gzeof($this->fh); 471 472 break; 473 474 case 'bz': 475 case 'f': 476 default: 477 $res = feof($this->fh); 478 479 break; 480 } 481 482 $error = error_get_last(); 483 484 if ($error !== null && $error['message'] !== '') 485 { 486 throw new FilesystemException($error['message']); 487 } 488 489 // Return the result 490 return $res; 491 } 492 493 /** 494 * Retrieve the file size of the path 495 * 496 * @return integer|boolean 497 * 498 * @since 1.0 499 * @throws FilesystemException 500 */ 501 public function filesize() 502 { 503 if (!$this->filename) 504 { 505 throw new FilesystemException('File not open'); 506 } 507 508 // Capture PHP errors 509 if (PHP_VERSION_ID < 70000) 510 { 511 // @Todo Remove this path, when PHP5 support is dropped. 512 set_error_handler( 513 function () { 514 return false; 515 } 516 ); 517 @trigger_error(''); 518 restore_error_handler(); 519 } 520 else 521 { 522 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 523 error_clear_last(); 524 } 525 526 $res = @filesize($this->filename); 527 528 if (!$res) 529 { 530 $res = Helper::remotefsize($this->filename); 531 } 532 533 if (!$res) 534 { 535 $error = error_get_last(); 536 537 if ($error === null || $error['message'] === '') 538 { 539 // Error but nothing from php? Create our own 540 $error = array( 541 'message' => 'Failed to get file size. This may not work for all streams.' 542 ); 543 } 544 545 throw new FilesystemException($error['message']); 546 } 547 548 $this->filesize = $res; 549 550 // Return the result 551 return $this->filesize; 552 } 553 554 /** 555 * Get a line from the stream source. 556 * 557 * @param integer $length The number of bytes (optional) to read. 558 * 559 * @return string 560 * 561 * @since 1.0 562 * @throws FilesystemException 563 */ 564 public function gets($length = 0) 565 { 566 if (!$this->fh) 567 { 568 throw new FilesystemException('File not open'); 569 } 570 571 // Capture PHP errors 572 if (PHP_VERSION_ID < 70000) 573 { 574 // @Todo Remove this path, when PHP5 support is dropped. 575 set_error_handler( 576 function () { 577 return false; 578 } 579 ); 580 @trigger_error(''); 581 restore_error_handler(); 582 } 583 else 584 { 585 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 586 error_clear_last(); 587 } 588 589 switch ($this->processingmethod) 590 { 591 case 'gz': 592 $res = $length ? gzgets($this->fh, $length) : gzgets($this->fh); 593 594 break; 595 596 case 'bz': 597 case 'f': 598 default: 599 $res = $length ? fgets($this->fh, $length) : fgets($this->fh); 600 601 break; 602 } 603 604 if (!$res) 605 { 606 $error = error_get_last(); 607 608 if ($error === null || $error['message'] === '') 609 { 610 // Error but nothing from php? Create our own 611 $error = array( 612 'message' => 'Unable to read from stream' 613 ); 614 } 615 616 throw new FilesystemException($error['message']); 617 } 618 619 // Return the result 620 return $res; 621 } 622 623 /** 624 * Read a file 625 * 626 * Handles user space streams appropriately otherwise any read will return 8192 627 * 628 * @param integer $length Length of data to read 629 * 630 * @return string 631 * 632 * @link https://www.php.net/manual/en/function.fread.php 633 * @since 1.0 634 * @throws FilesystemException 635 */ 636 public function read($length = 0) 637 { 638 if (!$this->fh) 639 { 640 throw new FilesystemException('File not open'); 641 } 642 643 if (!$this->filesize && !$length) 644 { 645 // Get the filesize 646 $this->filesize(); 647 648 if (!$this->filesize) 649 { 650 // Set it to the biggest and then wait until eof 651 $length = -1; 652 } 653 else 654 { 655 $length = $this->filesize; 656 } 657 } 658 659 $retval = false; 660 661 // Capture PHP errors 662 if (PHP_VERSION_ID < 70000) 663 { 664 // @Todo Remove this path, when PHP5 support is dropped. 665 set_error_handler( 666 function () { 667 return false; 668 } 669 ); 670 @trigger_error(''); 671 restore_error_handler(); 672 } 673 else 674 { 675 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 676 error_clear_last(); 677 } 678 679 $remaining = $length; 680 681 do 682 { 683 // Do chunked reads where relevant 684 switch ($this->processingmethod) 685 { 686 case 'bz': 687 $res = ($remaining > 0) ? bzread($this->fh, $remaining) : bzread($this->fh, $this->chunksize); 688 689 break; 690 691 case 'gz': 692 $res = ($remaining > 0) ? gzread($this->fh, $remaining) : gzread($this->fh, $this->chunksize); 693 694 break; 695 696 case 'f': 697 default: 698 $res = ($remaining > 0) ? fread($this->fh, $remaining) : fread($this->fh, $this->chunksize); 699 700 break; 701 } 702 703 if (!$res) 704 { 705 $error = error_get_last(); 706 707 if ($error === null || $error['message'] === '') 708 { 709 // Error but nothing from php? Create our own 710 $error = array( 711 'message' => 'Unable to read from stream' 712 ); 713 } 714 715 throw new FilesystemException($error['message']); 716 } 717 718 if (!$retval) 719 { 720 $retval = ''; 721 } 722 723 $retval .= $res; 724 725 if (!$this->eof()) 726 { 727 $len = \strlen($res); 728 $remaining -= $len; 729 } 730 else 731 { 732 // If it's the end of the file then we've nothing left to read; reset remaining and len 733 $remaining = 0; 734 $length = \strlen($retval); 735 } 736 } 737 while ($remaining || !$length); 738 739 // Return the result 740 return $retval; 741 } 742 743 /** 744 * Seek the file 745 * 746 * Note: the return value is different to that of fseek 747 * 748 * @param integer $offset Offset to use when seeking. 749 * @param integer $whence Seek mode to use. 750 * 751 * @return boolean True on success, false on failure 752 * 753 * @link https://www.php.net/manual/en/function.fseek.php 754 * @since 1.0 755 * @throws FilesystemException 756 */ 757 public function seek($offset, $whence = \SEEK_SET) 758 { 759 if (!$this->fh) 760 { 761 throw new FilesystemException('File not open'); 762 } 763 764 // Capture PHP errors 765 if (PHP_VERSION_ID < 70000) 766 { 767 // @Todo Remove this path, when PHP5 support is dropped. 768 set_error_handler( 769 function () { 770 return false; 771 } 772 ); 773 @trigger_error(''); 774 restore_error_handler(); 775 } 776 else 777 { 778 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 779 error_clear_last(); 780 } 781 782 switch ($this->processingmethod) 783 { 784 case 'gz': 785 $res = gzseek($this->fh, $offset, $whence); 786 787 break; 788 789 case 'bz': 790 case 'f': 791 default: 792 $res = fseek($this->fh, $offset, $whence); 793 794 break; 795 } 796 797 // Seek, interestingly, returns 0 on success or -1 on failure. 798 if ($res == -1) 799 { 800 $error = error_get_last(); 801 802 if ($error === null || $error['message'] === '') 803 { 804 // Error but nothing from php? Create our own 805 $error = array( 806 'message' => 'Unable to seek in stream' 807 ); 808 } 809 810 throw new FilesystemException($error['message']); 811 } 812 813 // Return the result 814 return true; 815 } 816 817 /** 818 * Returns the current position of the file read/write pointer. 819 * 820 * @return integer 821 * 822 * @since 1.0 823 * @throws FilesystemException 824 */ 825 public function tell() 826 { 827 if (!$this->fh) 828 { 829 throw new FilesystemException('File not open'); 830 } 831 832 // Capture PHP errors 833 if (PHP_VERSION_ID < 70000) 834 { 835 // @Todo Remove this path, when PHP5 support is dropped. 836 set_error_handler( 837 function () { 838 return false; 839 } 840 ); 841 @trigger_error(''); 842 restore_error_handler(); 843 } 844 else 845 { 846 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 847 error_clear_last(); 848 } 849 850 switch ($this->processingmethod) 851 { 852 case 'gz': 853 $res = gztell($this->fh); 854 855 break; 856 857 case 'bz': 858 case 'f': 859 default: 860 $res = ftell($this->fh); 861 862 break; 863 } 864 865 // May return 0 so check if it's really false 866 if ($res === false) 867 { 868 $error = error_get_last(); 869 870 if ($error === null || $error['message'] === '') 871 { 872 // Error but nothing from php? Create our own 873 $error = array( 874 'message' => 'Unable to determine the current position in stream' 875 ); 876 } 877 878 throw new FilesystemException($error['message']); 879 } 880 881 // Return the result 882 return $res; 883 } 884 885 /** 886 * File write 887 * 888 * Whilst this function accepts a reference, the underlying fwrite 889 * will do a copy! This will roughly double the memory allocation for 890 * any write you do. Specifying chunked will get around this by only 891 * writing in specific chunk sizes. This defaults to 8192 which is a 892 * sane number to use most of the time (change the default with 893 * Stream::set('chunksize', newsize);) 894 * Note: This doesn't support gzip/bzip2 writing like reading does 895 * 896 * @param string $string Reference to the string to write. 897 * @param integer $length Length of the string to write. 898 * @param integer $chunk Size of chunks to write in. 899 * 900 * @return boolean 901 * 902 * @link https://www.php.net/manual/en/function.fwrite.php 903 * @since 1.0 904 * @throws FilesystemException 905 */ 906 public function write(&$string, $length = 0, $chunk = 0) 907 { 908 if (!$this->fh) 909 { 910 throw new FilesystemException('File not open'); 911 } 912 913 if ($this->openmode == 'r') 914 { 915 throw new \RuntimeException('File is in readonly mode'); 916 } 917 918 // If the length isn't set, set it to the length of the string. 919 if (!$length) 920 { 921 $length = \strlen($string); 922 } 923 924 // If the chunk isn't set, set it to the default. 925 if (!$chunk) 926 { 927 $chunk = $this->chunksize; 928 } 929 930 $retval = true; 931 932 // Capture PHP errors 933 if (PHP_VERSION_ID < 70000) 934 { 935 // @Todo Remove this path, when PHP5 support is dropped. 936 set_error_handler( 937 function () { 938 return false; 939 } 940 ); 941 @trigger_error(''); 942 restore_error_handler(); 943 } 944 else 945 { 946 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 947 error_clear_last(); 948 } 949 950 $remaining = $length; 951 $start = 0; 952 953 do 954 { 955 // If the amount remaining is greater than the chunk size, then use the chunk 956 $amount = ($remaining > $chunk) ? $chunk : $remaining; 957 $res = fwrite($this->fh, substr($string, $start), $amount); 958 959 // Returns false on error or the number of bytes written 960 if ($res === false) 961 { 962 $error = error_get_last(); 963 964 if ($error === null || $error['message'] === '') 965 { 966 // Error but nothing from php? Create our own 967 $error = array( 968 'message' => 'Unable to write to stream' 969 ); 970 } 971 972 throw new FilesystemException($error['message']); 973 } 974 975 if ($res === 0) 976 { 977 // Wrote nothing? 978 throw new FilesystemException('Warning: No data written'); 979 } 980 981 // Wrote something 982 $start += $amount; 983 $remaining -= $res; 984 } 985 while ($remaining); 986 987 // Return the result 988 return $retval; 989 } 990 991 /** 992 * Chmod wrapper 993 * 994 * @param string $filename File name. 995 * @param mixed $mode Mode to use. 996 * 997 * @return boolean 998 * 999 * @since 1.0 1000 * @throws FilesystemException 1001 */ 1002 public function chmod($filename = '', $mode = 0) 1003 { 1004 if (!$filename) 1005 { 1006 if (!isset($this->filename) || !$this->filename) 1007 { 1008 throw new FilesystemException('Filename not set'); 1009 } 1010 1011 $filename = $this->filename; 1012 } 1013 1014 // If no mode is set use the default 1015 if (!$mode) 1016 { 1017 $mode = $this->filemode; 1018 } 1019 1020 // Capture PHP errors 1021 if (PHP_VERSION_ID < 70000) 1022 { 1023 // @Todo Remove this path, when PHP5 support is dropped. 1024 set_error_handler( 1025 function () { 1026 return false; 1027 } 1028 ); 1029 @trigger_error(''); 1030 restore_error_handler(); 1031 } 1032 else 1033 { 1034 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 1035 error_clear_last(); 1036 } 1037 1038 $sch = parse_url($filename, \PHP_URL_SCHEME); 1039 1040 // Scheme specific options; ftp's chmod support is fun. 1041 switch ($sch) 1042 { 1043 case 'ftp': 1044 case 'ftps': 1045 $res = Helper::ftpChmod($filename, $mode); 1046 1047 break; 1048 1049 default: 1050 $res = chmod($filename, $mode); 1051 1052 break; 1053 } 1054 1055 if ($res === false) 1056 { 1057 $error = error_get_last(); 1058 1059 if ($error === null || $error['message'] === '') 1060 { 1061 // Error but nothing from php? Create our own 1062 $error = array( 1063 'message' => 'Unable to change mode of stream' 1064 ); 1065 } 1066 1067 throw new FilesystemException($error['message']); 1068 } 1069 1070 // Return the result 1071 return true; 1072 } 1073 1074 /** 1075 * Get the stream metadata 1076 * 1077 * @return array header/metadata 1078 * 1079 * @link https://www.php.net/manual/en/function.stream-get-meta-data.php 1080 * @since 1.0 1081 * @throws FilesystemException 1082 */ 1083 public function get_meta_data() 1084 { 1085 if (!$this->fh) 1086 { 1087 throw new FilesystemException('File not open'); 1088 } 1089 1090 return stream_get_meta_data($this->fh); 1091 } 1092 1093 /** 1094 * Stream contexts 1095 * Builds the context from the array 1096 * 1097 * @return void 1098 * 1099 * @since 1.0 1100 */ 1101 public function _buildContext() 1102 { 1103 // According to the manual this always works! 1104 if (\count($this->contextOptions)) 1105 { 1106 $this->context = @stream_context_create($this->contextOptions); 1107 } 1108 else 1109 { 1110 $this->context = null; 1111 } 1112 } 1113 1114 /** 1115 * Updates the context to the array 1116 * 1117 * Format is the same as the options for stream_context_create 1118 * 1119 * @param array $context Options to create the context with 1120 * 1121 * @return void 1122 * 1123 * @link https://www.php.net/stream_context_create 1124 * @since 1.0 1125 */ 1126 public function setContextOptions($context) 1127 { 1128 $this->contextOptions = $context; 1129 $this->_buildContext(); 1130 } 1131 1132 /** 1133 * Adds a particular options to the context 1134 * 1135 * @param string $wrapper The wrapper to use 1136 * @param string $name The option to set 1137 * @param string $value The value of the option 1138 * 1139 * @return void 1140 * 1141 * @link https://www.php.net/stream_context_create Stream Context Creation 1142 * @link https://www.php.net/manual/en/context.php Context Options for various streams 1143 * @since 1.0 1144 */ 1145 public function addContextEntry($wrapper, $name, $value) 1146 { 1147 $this->contextOptions[$wrapper][$name] = $value; 1148 $this->_buildContext(); 1149 } 1150 1151 /** 1152 * Deletes a particular setting from a context 1153 * 1154 * @param string $wrapper The wrapper to use 1155 * @param string $name The option to unset 1156 * 1157 * @return void 1158 * 1159 * @link https://www.php.net/stream_context_create 1160 * @since 1.0 1161 */ 1162 public function deleteContextEntry($wrapper, $name) 1163 { 1164 // Check whether the wrapper is set 1165 if (isset($this->contextOptions[$wrapper])) 1166 { 1167 // Check that entry is set for that wrapper 1168 if (isset($this->contextOptions[$wrapper][$name])) 1169 { 1170 // Unset the item 1171 unset($this->contextOptions[$wrapper][$name]); 1172 1173 // Check that there are still items there 1174 if (!\count($this->contextOptions[$wrapper])) 1175 { 1176 // Clean up an empty wrapper context option 1177 unset($this->contextOptions[$wrapper]); 1178 } 1179 } 1180 } 1181 1182 // Rebuild the context and apply it to the stream 1183 $this->_buildContext(); 1184 } 1185 1186 /** 1187 * Applies the current context to the stream 1188 * 1189 * Use this to change the values of the context after you've opened a stream 1190 * 1191 * @return boolean 1192 * 1193 * @since 1.0 1194 * @throws FilesystemException 1195 */ 1196 public function applyContextToStream() 1197 { 1198 $retval = false; 1199 1200 if ($this->fh) 1201 { 1202 // Capture PHP errors 1203 if (PHP_VERSION_ID < 70000) 1204 { 1205 // @Todo Remove this path, when PHP5 support is dropped. 1206 set_error_handler( 1207 function () { 1208 return false; 1209 } 1210 ); 1211 @trigger_error(''); 1212 restore_error_handler(); 1213 } 1214 else 1215 { 1216 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 1217 error_clear_last(); 1218 } 1219 1220 $retval = @stream_context_set_option($this->fh, $this->contextOptions); 1221 1222 if (!$retval) 1223 { 1224 $error = error_get_last(); 1225 1226 if ($error === null || $error['message'] === '') 1227 { 1228 // Error but nothing from php? Create our own 1229 $error = array( 1230 'message' => 'Unable to apply context to stream' 1231 ); 1232 } 1233 1234 throw new FilesystemException($error['message']); 1235 } 1236 } 1237 1238 return $retval; 1239 } 1240 1241 /** 1242 * Stream filters 1243 * Append a filter to the chain 1244 * 1245 * @param string $filtername The key name of the filter. 1246 * @param integer $readWrite Optional. Defaults to STREAM_FILTER_READ. 1247 * @param array $params An array of params for the stream_filter_append call. 1248 * 1249 * @return resource|boolean 1250 * 1251 * @link https://www.php.net/manual/en/function.stream-filter-append.php 1252 * @since 1.0 1253 * @throws FilesystemException 1254 */ 1255 public function appendFilter($filtername, $readWrite = \STREAM_FILTER_READ, $params = []) 1256 { 1257 $res = false; 1258 1259 if ($this->fh) 1260 { 1261 // Capture PHP errors 1262 if (PHP_VERSION_ID < 70000) 1263 { 1264 // @Todo Remove this path, when PHP5 support is dropped. 1265 set_error_handler( 1266 function () { 1267 return false; 1268 } 1269 ); 1270 @trigger_error(''); 1271 restore_error_handler(); 1272 } 1273 else 1274 { 1275 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 1276 error_clear_last(); 1277 } 1278 1279 $res = @stream_filter_append($this->fh, $filtername, $readWrite, $params); 1280 1281 if (!$res) 1282 { 1283 $error = error_get_last(); 1284 1285 if ($error !== null && $error['message'] !== '') 1286 { 1287 throw new FilesystemException($error['message']); 1288 } 1289 } 1290 1291 $this->filters[] = &$res; 1292 } 1293 1294 return $res; 1295 } 1296 1297 /** 1298 * Prepend a filter to the chain 1299 * 1300 * @param string $filtername The key name of the filter. 1301 * @param integer $readWrite Optional. Defaults to STREAM_FILTER_READ. 1302 * @param array $params An array of params for the stream_filter_prepend call. 1303 * 1304 * @return resource|boolean 1305 * 1306 * @link https://www.php.net/manual/en/function.stream-filter-prepend.php 1307 * @since 1.0 1308 * @throws FilesystemException 1309 */ 1310 public function prependFilter($filtername, $readWrite = \STREAM_FILTER_READ, $params = []) 1311 { 1312 $res = false; 1313 1314 if ($this->fh) 1315 { 1316 // Capture PHP errors 1317 if (PHP_VERSION_ID < 70000) 1318 { 1319 // @Todo Remove this path, when PHP5 support is dropped. 1320 set_error_handler( 1321 function () { 1322 return false; 1323 } 1324 ); 1325 @trigger_error(''); 1326 restore_error_handler(); 1327 } 1328 else 1329 { 1330 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 1331 error_clear_last(); 1332 } 1333 1334 $res = @stream_filter_prepend($this->fh, $filtername, $readWrite, $params); 1335 1336 if (!$res) 1337 { 1338 $error = error_get_last(); 1339 1340 if ($error !== null && $error['message'] !== '') 1341 { 1342 throw new FilesystemException($error['message']); 1343 } 1344 } 1345 1346 array_unshift($this->filters, ''); 1347 $this->filters[0] = &$res; 1348 } 1349 1350 return $res; 1351 } 1352 1353 /** 1354 * Remove a filter, either by resource (handed out from the append or prepend function) 1355 * or via getting the filter list) 1356 * 1357 * @param resource $resource The resource. 1358 * @param boolean $byindex The index of the filter. 1359 * 1360 * @return boolean Result of operation 1361 * 1362 * @since 1.0 1363 * @throws FilesystemException 1364 */ 1365 public function removeFilter(&$resource, $byindex = false) 1366 { 1367 // Capture PHP errors 1368 if (PHP_VERSION_ID < 70000) 1369 { 1370 // @Todo Remove this path, when PHP5 support is dropped. 1371 set_error_handler( 1372 function () { 1373 return false; 1374 } 1375 ); 1376 @trigger_error(''); 1377 restore_error_handler(); 1378 } 1379 else 1380 { 1381 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 1382 error_clear_last(); 1383 } 1384 1385 if ($byindex) 1386 { 1387 $res = stream_filter_remove($this->filters[$resource]); 1388 } 1389 else 1390 { 1391 $res = stream_filter_remove($resource); 1392 } 1393 1394 if (!$res) 1395 { 1396 $error = error_get_last(); 1397 1398 if ($error === null || $error['message'] === '') 1399 { 1400 // Error but nothing from php? Create our own 1401 $error = array( 1402 'message' => 'Unable to remove filter from stream' 1403 ); 1404 } 1405 1406 throw new FilesystemException($error['message']); 1407 } 1408 1409 return $res; 1410 } 1411 1412 /** 1413 * Copy a file from src to dest 1414 * 1415 * @param string $src The file path to copy from. 1416 * @param string $dest The file path to copy to. 1417 * @param resource $context A valid context resource (optional) created with stream_context_create. 1418 * @param boolean $usePrefix Controls the use of a prefix (optional). 1419 * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped. 1420 * 1421 * @return boolean 1422 * 1423 * @since 1.0 1424 * @throws FilesystemException 1425 */ 1426 public function copy($src, $dest, $context = null, $usePrefix = true, $relative = false) 1427 { 1428 // Capture PHP errors 1429 if (PHP_VERSION_ID < 70000) 1430 { 1431 // @Todo Remove this path, when PHP5 support is dropped. 1432 set_error_handler( 1433 function () { 1434 return false; 1435 } 1436 ); 1437 @trigger_error(''); 1438 restore_error_handler(); 1439 } 1440 else 1441 { 1442 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 1443 error_clear_last(); 1444 } 1445 1446 $chmodDest = $this->_getFilename($dest, 'w', $usePrefix, $relative); 1447 1448 // Since we're going to open the file directly we need to get the filename. 1449 // We need to use the same prefix so force everything to write. 1450 $src = $this->_getFilename($src, 'w', $usePrefix, $relative); 1451 $dest = $this->_getFilename($dest, 'w', $usePrefix, $relative); 1452 1453 // One supplied at copy; overrides everything 1454 if ($context) 1455 { 1456 // Use the provided context 1457 $res = @copy($src, $dest, $context); 1458 } 1459 elseif ($this->context) 1460 { 1461 // One provided at initialisation 1462 $res = @copy($src, $dest, $this->context); 1463 } 1464 else 1465 { 1466 // No context; all defaults 1467 $res = @copy($src, $dest); 1468 } 1469 1470 if (!$res) 1471 { 1472 $error = error_get_last(); 1473 1474 if ($error !== null && $error['message'] !== '') 1475 { 1476 throw new FilesystemException($error['message']); 1477 } 1478 } 1479 1480 $this->chmod($chmodDest); 1481 1482 return $res; 1483 } 1484 1485 /** 1486 * Moves a file 1487 * 1488 * @param string $src The file path to move from. 1489 * @param string $dest The file path to move to. 1490 * @param resource $context A valid context resource (optional) created with stream_context_create. 1491 * @param boolean $usePrefix Controls the use of a prefix (optional). 1492 * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped. 1493 * 1494 * @return boolean 1495 * 1496 * @since 1.0 1497 * @throws FilesystemException 1498 */ 1499 public function move($src, $dest, $context = null, $usePrefix = true, $relative = false) 1500 { 1501 // Capture PHP errors 1502 if (PHP_VERSION_ID < 70000) 1503 { 1504 // @Todo Remove this path, when PHP5 support is dropped. 1505 set_error_handler( 1506 function () { 1507 return false; 1508 } 1509 ); 1510 @trigger_error(''); 1511 restore_error_handler(); 1512 } 1513 else 1514 { 1515 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 1516 error_clear_last(); 1517 } 1518 1519 $src = $this->_getFilename($src, 'w', $usePrefix, $relative); 1520 $dest = $this->_getFilename($dest, 'w', $usePrefix, $relative); 1521 1522 if ($context) 1523 { 1524 // Use the provided context 1525 $res = @rename($src, $dest, $context); 1526 } 1527 elseif ($this->context) 1528 { 1529 // Use the object's context 1530 $res = @rename($src, $dest, $this->context); 1531 } 1532 else 1533 { 1534 // Don't use any context 1535 $res = @rename($src, $dest); 1536 } 1537 1538 if (!$res) 1539 { 1540 $error = error_get_last(); 1541 1542 if ($error === null || $error['message'] === '') 1543 { 1544 // Error but nothing from php? Create our own 1545 $error = array( 1546 'message' => 'Unable to move stream' 1547 ); 1548 } 1549 1550 throw new FilesystemException($error['message']); 1551 } 1552 1553 $this->chmod($dest); 1554 1555 return $res; 1556 } 1557 1558 /** 1559 * Delete a file 1560 * 1561 * @param string $filename The file path to delete. 1562 * @param resource $context A valid context resource (optional) created with stream_context_create. 1563 * @param boolean $usePrefix Controls the use of a prefix (optional). 1564 * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped. 1565 * 1566 * @return boolean 1567 * 1568 * @since 1.0 1569 * @throws FilesystemException 1570 */ 1571 public function delete($filename, $context = null, $usePrefix = true, $relative = false) 1572 { 1573 // Capture PHP errors 1574 if (PHP_VERSION_ID < 70000) 1575 { 1576 // @Todo Remove this path, when PHP5 support is dropped. 1577 set_error_handler( 1578 function () { 1579 return false; 1580 } 1581 ); 1582 @trigger_error(''); 1583 restore_error_handler(); 1584 } 1585 else 1586 { 1587 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 1588 error_clear_last(); 1589 } 1590 1591 $filename = $this->_getFilename($filename, 'w', $usePrefix, $relative); 1592 1593 if ($context) 1594 { 1595 // Use the provided context 1596 $res = @unlink($filename, $context); 1597 } 1598 elseif ($this->context) 1599 { 1600 // Use the object's context 1601 $res = @unlink($filename, $this->context); 1602 } 1603 else 1604 { 1605 // Don't use any context 1606 $res = @unlink($filename); 1607 } 1608 1609 if (!$res) 1610 { 1611 $error = error_get_last(); 1612 1613 if ($error === null || $error['message'] === '') 1614 { 1615 // Error but nothing from php? Create our own 1616 $error = array( 1617 'message' => 'Unable to delete stream' 1618 ); 1619 } 1620 1621 throw new FilesystemException($error['message']); 1622 } 1623 1624 return $res; 1625 } 1626 1627 /** 1628 * Upload a file 1629 * 1630 * @param string $src The file path to copy from (usually a temp folder). 1631 * @param string $dest The file path to copy to. 1632 * @param resource $context A valid context resource (optional) created with stream_context_create. 1633 * @param boolean $usePrefix Controls the use of a prefix (optional). 1634 * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped. 1635 * 1636 * @return boolean 1637 * 1638 * @since 1.0 1639 * @throws FilesystemException 1640 */ 1641 public function upload($src, $dest, $context = null, $usePrefix = true, $relative = false) 1642 { 1643 if (is_uploaded_file($src)) 1644 { 1645 // Make sure it's an uploaded file 1646 return $this->copy($src, $dest, $context, $usePrefix, $relative); 1647 } 1648 1649 throw new FilesystemException('Not an uploaded file.'); 1650 } 1651 1652 /** 1653 * Writes a chunk of data to a file. 1654 * 1655 * @param string $filename The file name. 1656 * @param string $buffer The data to write to the file. 1657 * @param boolean $appendToFile Append to the file and not overwrite it. 1658 * 1659 * @return boolean 1660 * 1661 * @since 1.0 1662 */ 1663 public function writeFile($filename, &$buffer, $appendToFile = false) 1664 { 1665 $fileMode = 'w'; 1666 1667 // Switch the filemode when we want to append to the file 1668 if ($appendToFile) 1669 { 1670 $fileMode = 'a'; 1671 } 1672 1673 if ($this->open($filename, $fileMode)) 1674 { 1675 $result = $this->write($buffer); 1676 $this->chmod(); 1677 $this->close(); 1678 1679 return $result; 1680 } 1681 1682 return false; 1683 } 1684 1685 /** 1686 * Determine the appropriate 'filename' of a file 1687 * 1688 * @param string $filename Original filename of the file 1689 * @param string $mode Mode string to retrieve the filename 1690 * @param boolean $usePrefix Controls the use of a prefix 1691 * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped. 1692 * 1693 * @return string 1694 * 1695 * @since 1.0 1696 */ 1697 public function _getFilename($filename, $mode, $usePrefix, $relative) 1698 { 1699 if ($usePrefix) 1700 { 1701 // Get rid of binary or t, should be at the end of the string 1702 $tmode = trim($mode, 'btf123456789'); 1703 1704 $stream = explode('://', $filename, 2); 1705 $scheme = ''; 1706 $filename = $stream[0]; 1707 1708 if (\count($stream) >= 2) 1709 { 1710 $scheme = $stream[0] . '://'; 1711 $filename = $stream[1]; 1712 } 1713 1714 // Check if it's a write mode then add the appropriate prefix 1715 if (\in_array($tmode, Helper::getWriteModes())) 1716 { 1717 $prefixToUse = $this->writeprefix; 1718 } 1719 else 1720 { 1721 $prefixToUse = $this->readprefix; 1722 } 1723 1724 // Get rid of JPATH_ROOT (legacy compat) 1725 if (!$relative && $prefixToUse) 1726 { 1727 $pos = strpos($filename, JPATH_ROOT); 1728 1729 if ($pos !== false) 1730 { 1731 $filename = substr_replace($filename, '', $pos, \strlen(JPATH_ROOT)); 1732 } 1733 } 1734 1735 $filename = ($prefixToUse ? $prefixToUse : '') . $filename; 1736 } 1737 1738 return $filename; 1739 } 1740 1741 /** 1742 * Return the internal file handle 1743 * 1744 * @return File handler 1745 * 1746 * @since 1.0 1747 */ 1748 public function getFileHandle() 1749 { 1750 return $this->fh; 1751 } 1752 1753 /** 1754 * Modifies a property of the object, creating it if it does not already exist. 1755 * 1756 * @param string $property The name of the property. 1757 * @param mixed $value The value of the property to set. 1758 * 1759 * @return mixed Previous value of the property. 1760 * 1761 * @since 1.0 1762 */ 1763 public function set($property, $value = null) 1764 { 1765 $previous = $this->$property ?? null; 1766 $this->$property = $value; 1767 1768 return $previous; 1769 } 1770 1771 /** 1772 * Returns a property of the object or the default value if the property is not set. 1773 * 1774 * @param string $property The name of the property. 1775 * @param mixed $default The default value. 1776 * 1777 * @return mixed The value of the property. 1778 * 1779 * @since 1.0 1780 */ 1781 public function get($property, $default = null) 1782 { 1783 if (isset($this->$property)) 1784 { 1785 return $this->$property; 1786 } 1787 1788 return $default; 1789 } 1790 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |