[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/joomla/filesystem/src/ -> Stream.php (source)

   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  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer