PageRenderTime 53ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Joomla/Filesystem/Stream.php

https://github.com/piotr-cz/joomla-framework
PHP | 1451 lines | 716 code | 177 blank | 558 comment | 87 complexity | 0d64984f5d6d29ad994698da7a20af27 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Part of the Joomla Framework Filesystem Package
  4. *
  5. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  6. * @license GNU General Public License version 2 or later; see LICENSE
  7. */
  8. namespace Joomla\Filesystem;
  9. /**
  10. * Joomla! Stream Interface
  11. *
  12. * The Joomla! stream interface is designed to handle files as streams
  13. * where as the legacy JFile static class treated files in a rather
  14. * atomic manner.
  15. *
  16. * This class adheres to the stream wrapper operations:
  17. *
  18. * @see http://php.net/manual/en/function.stream-get-wrappers.php
  19. * @see http://php.net/manual/en/intro.stream.php PHP Stream Manual
  20. * @see http://php.net/manual/en/wrappers.php Stream Wrappers
  21. * @see http://php.net/manual/en/filters.php Stream Filters
  22. * @see http://php.net/manual/en/transports.php Socket Transports (used by some options, particularly HTTP proxy)
  23. * @since 1.0
  24. */
  25. class Stream
  26. {
  27. /**
  28. * File Mode
  29. *
  30. * @var integer
  31. * @since 1.0
  32. */
  33. protected $filemode = 0644;
  34. /**
  35. * Directory Mode
  36. *
  37. * @var integer
  38. * @since 1.0
  39. */
  40. protected $dirmode = 0755;
  41. /**
  42. * Default Chunk Size
  43. *
  44. * @var integer
  45. * @since 1.0
  46. */
  47. protected $chunksize = 8192;
  48. /**
  49. * Filename
  50. *
  51. * @var string
  52. * @since 1.0
  53. */
  54. protected $filename;
  55. /**
  56. * Prefix of the connection for writing
  57. *
  58. * @var string
  59. * @since 1.0
  60. */
  61. protected $writeprefix;
  62. /**
  63. * Prefix of the connection for reading
  64. *
  65. * @var string
  66. * @since 1.0
  67. */
  68. protected $readprefix;
  69. /**
  70. * Read Processing method
  71. *
  72. * @var string gz, bz, f
  73. * If a scheme is detected, fopen will be defaulted
  74. * To use compression with a network stream use a filter
  75. * @since 1.0
  76. */
  77. protected $processingmethod = 'f';
  78. /**
  79. * Filters applied to the current stream
  80. *
  81. * @var array
  82. * @since 1.0
  83. */
  84. protected $filters = array();
  85. /**
  86. * File Handle
  87. *
  88. * @var array
  89. * @since 1.0
  90. */
  91. protected $fh;
  92. /**
  93. * File size
  94. *
  95. * @var integer
  96. * @since 1.0
  97. */
  98. protected $filesize;
  99. /**
  100. * Context to use when opening the connection
  101. *
  102. * @var
  103. * @since 1.0
  104. */
  105. protected $context = null;
  106. /**
  107. * Context options; used to rebuild the context
  108. *
  109. * @var
  110. * @since 1.0
  111. */
  112. protected $contextOptions;
  113. /**
  114. * The mode under which the file was opened
  115. *
  116. * @var
  117. * @since 1.0
  118. */
  119. protected $openmode;
  120. /**
  121. * Constructor
  122. *
  123. * @param string $writeprefix Prefix of the stream (optional). Unlike the JPATH_*, this has a final path separator!
  124. * @param string $readprefix The read prefix (optional).
  125. * @param array $context The context options (optional).
  126. *
  127. * @since 1.0
  128. */
  129. public function __construct($writeprefix = '', $readprefix = '', $context = array())
  130. {
  131. $this->writeprefix = $writeprefix;
  132. $this->readprefix = $readprefix;
  133. $this->contextOptions = $context;
  134. $this->_buildContext();
  135. }
  136. /**
  137. * Destructor
  138. *
  139. * @since 1.0
  140. */
  141. public function __destruct()
  142. {
  143. // Attempt to close on destruction if there is a file handle
  144. if ($this->fh)
  145. {
  146. @$this->close();
  147. }
  148. }
  149. /**
  150. * Creates a new stream object with appropriate prefix
  151. *
  152. * @param boolean $use_prefix Prefix the connections for writing
  153. * @param string $ua UA User agent to use
  154. * @param boolean $uamask User agent masking (prefix Mozilla)
  155. *
  156. * @return Stream
  157. *
  158. * @see Stream
  159. * @since 1.0
  160. */
  161. public static function getStream($use_prefix = true, $ua = null, $uamask = false)
  162. {
  163. // Setup the context; Joomla! UA and overwrite
  164. $context = array();
  165. // Set the UA for HTTP
  166. $context['http']['user_agent'] = $ua ?: 'Joomla! Framework Stream';
  167. if ($use_prefix)
  168. {
  169. return new Stream(JPATH_ROOT . '/', JPATH_ROOT, $context);
  170. }
  171. return new Stream('', '', $context);
  172. }
  173. /**
  174. * Generic File Operations
  175. *
  176. * Open a stream with some lazy loading smarts
  177. *
  178. * @param string $filename Filename
  179. * @param string $mode Mode string to use
  180. * @param boolean $use_include_path Use the PHP include path
  181. * @param resource $context Context to use when opening
  182. * @param boolean $use_prefix Use a prefix to open the file
  183. * @param boolean $relative Filename is a relative path (if false, strips JPATH_ROOT to make it relative)
  184. * @param boolean $detectprocessingmode Detect the processing method for the file and use the appropriate function
  185. * to handle output automatically
  186. *
  187. * @return boolean
  188. *
  189. * @since 1.0
  190. * @throws \RuntimeException
  191. */
  192. public function open($filename, $mode = 'r', $use_include_path = false, $context = null,
  193. $use_prefix = false, $relative = false, $detectprocessingmode = false)
  194. {
  195. $filename = $this->_getFilename($filename, $mode, $use_prefix, $relative);
  196. if (!$filename)
  197. {
  198. throw new \RuntimeException('Filename not set');
  199. }
  200. $this->filename = $filename;
  201. $this->openmode = $mode;
  202. $url = parse_url($filename);
  203. if (isset($url['scheme']))
  204. {
  205. // If we're dealing with a Joomla! stream, load it
  206. if (Helper::isJoomlaStream($url['scheme']))
  207. {
  208. require_once __DIR__ . '/streams/' . $url['scheme'] . '.php';
  209. }
  210. // We have a scheme! force the method to be f
  211. $this->processingmethod = 'f';
  212. }
  213. elseif ($detectprocessingmode)
  214. {
  215. $ext = strtolower(pathinfo($this->filename, PATHINFO_EXTENSION));
  216. switch ($ext)
  217. {
  218. case 'tgz':
  219. case 'gz':
  220. case 'gzip':
  221. $this->processingmethod = 'gz';
  222. break;
  223. case 'tbz2':
  224. case 'bz2':
  225. case 'bzip2':
  226. $this->processingmethod = 'bz';
  227. break;
  228. default:
  229. $this->processingmethod = 'f';
  230. break;
  231. }
  232. }
  233. // Capture PHP errors
  234. $php_errormsg = 'Error Unknown whilst opening a file';
  235. $track_errors = ini_get('track_errors');
  236. ini_set('track_errors', true);
  237. // Decide which context to use:
  238. switch ($this->processingmethod)
  239. {
  240. // Gzip doesn't support contexts or streams
  241. case 'gz':
  242. $this->fh = gzopen($filename, $mode, $use_include_path);
  243. break;
  244. // Bzip2 is much like gzip except it doesn't use the include path
  245. case 'bz':
  246. $this->fh = bzopen($filename, $mode);
  247. break;
  248. // Fopen can handle streams
  249. case 'f':
  250. default:
  251. // One supplied at open; overrides everything
  252. if ($context)
  253. {
  254. $this->fh = fopen($filename, $mode, $use_include_path, $context);
  255. }
  256. elseif ($this->context)
  257. // One provided at initialisation
  258. {
  259. $this->fh = fopen($filename, $mode, $use_include_path, $this->context);
  260. }
  261. else
  262. // No context; all defaults
  263. {
  264. $this->fh = fopen($filename, $mode, $use_include_path);
  265. }
  266. break;
  267. }
  268. if (!$this->fh)
  269. {
  270. throw new \RuntimeException($php_errormsg);
  271. }
  272. // Restore error tracking to what it was before
  273. ini_set('track_errors', $track_errors);
  274. // Return the result
  275. return true;
  276. }
  277. /**
  278. * Attempt to close a file handle
  279. *
  280. * Will return false if it failed and true on success
  281. * If the file is not open the system will return true, this function destroys the file handle as well
  282. *
  283. * @return boolean
  284. *
  285. * @since 1.0
  286. * @throws \RuntimeException
  287. */
  288. public function close()
  289. {
  290. if (!$this->fh)
  291. {
  292. throw new \RuntimeException('File not open');
  293. }
  294. // Capture PHP errors
  295. $php_errormsg = 'Error Unknown';
  296. $track_errors = ini_get('track_errors');
  297. ini_set('track_errors', true);
  298. switch ($this->processingmethod)
  299. {
  300. case 'gz':
  301. $res = gzclose($this->fh);
  302. break;
  303. case 'bz':
  304. $res = bzclose($this->fh);
  305. break;
  306. case 'f':
  307. default:
  308. $res = fclose($this->fh);
  309. break;
  310. }
  311. if (!$res)
  312. {
  313. throw new \RuntimeException($php_errormsg);
  314. }
  315. else
  316. {
  317. // Reset this
  318. $this->fh = null;
  319. }
  320. // If we wrote, chmod the file after it's closed
  321. if ($this->openmode[0] == 'w')
  322. {
  323. $this->chmod();
  324. }
  325. // Restore error tracking to what it was before
  326. ini_set('track_errors', $track_errors);
  327. // Return the result
  328. return true;
  329. }
  330. /**
  331. * Work out if we're at the end of the file for a stream
  332. *
  333. * @return boolean
  334. *
  335. * @since 1.0
  336. * @throws \RuntimeException
  337. */
  338. public function eof()
  339. {
  340. if (!$this->fh)
  341. {
  342. throw new \RuntimeException('File not open');
  343. }
  344. // Capture PHP errors
  345. $php_errormsg = '';
  346. $track_errors = ini_get('track_errors');
  347. ini_set('track_errors', true);
  348. switch ($this->processingmethod)
  349. {
  350. case 'gz':
  351. $res = gzeof($this->fh);
  352. break;
  353. case 'bz':
  354. case 'f':
  355. default:
  356. $res = feof($this->fh);
  357. break;
  358. }
  359. if ($php_errormsg)
  360. {
  361. throw new \RuntimeException($php_errormsg);
  362. }
  363. // Restore error tracking to what it was before
  364. ini_set('track_errors', $track_errors);
  365. // Return the result
  366. return $res;
  367. }
  368. /**
  369. * Retrieve the file size of the path
  370. *
  371. * @return mixed
  372. *
  373. * @since 1.0
  374. * @throws \RuntimeException
  375. */
  376. public function filesize()
  377. {
  378. if (!$this->filename)
  379. {
  380. throw new \RuntimeException('File not open');
  381. }
  382. // Capture PHP errors
  383. $php_errormsg = '';
  384. $track_errors = ini_get('track_errors');
  385. ini_set('track_errors', true);
  386. $res = @filesize($this->filename);
  387. if (!$res)
  388. {
  389. $tmp_error = '';
  390. if ($php_errormsg)
  391. {
  392. // Something went wrong.
  393. // Store the error in case we need it.
  394. $tmp_error = $php_errormsg;
  395. }
  396. $res = Helper::remotefsize($this->filename);
  397. if (!$res)
  398. {
  399. if ($tmp_error)
  400. {
  401. // Use the php_errormsg from before
  402. throw new \RuntimeException($tmp_error);
  403. }
  404. else
  405. {
  406. // Error but nothing from php? How strange! Create our own
  407. throw new \RuntimeException('Failed to get file size. This may not work for all streams.');
  408. }
  409. }
  410. else
  411. {
  412. $this->filesize = $res;
  413. $retval = $res;
  414. }
  415. }
  416. else
  417. {
  418. $this->filesize = $res;
  419. $retval = $res;
  420. }
  421. // Restore error tracking to what it was before.
  422. ini_set('track_errors', $track_errors);
  423. // Return the result
  424. return $retval;
  425. }
  426. /**
  427. * Get a line from the stream source.
  428. *
  429. * @param integer $length The number of bytes (optional) to read.
  430. *
  431. * @return mixed
  432. *
  433. * @since 1.0
  434. * @throws \RuntimeException
  435. */
  436. public function gets($length = 0)
  437. {
  438. if (!$this->fh)
  439. {
  440. throw new \RuntimeException('File not open');
  441. }
  442. // Capture PHP errors
  443. $php_errormsg = 'Error Unknown';
  444. $track_errors = ini_get('track_errors');
  445. ini_set('track_errors', true);
  446. switch ($this->processingmethod)
  447. {
  448. case 'gz':
  449. $res = $length ? gzgets($this->fh, $length) : gzgets($this->fh);
  450. break;
  451. case 'bz':
  452. case 'f':
  453. default:
  454. $res = $length ? fgets($this->fh, $length) : fgets($this->fh);
  455. break;
  456. }
  457. if (!$res)
  458. {
  459. throw new \RuntimeException($php_errormsg);
  460. }
  461. // Restore error tracking to what it was before
  462. ini_set('track_errors', $track_errors);
  463. // Return the result
  464. return $res;
  465. }
  466. /**
  467. * Read a file
  468. *
  469. * Handles user space streams appropriately otherwise any read will return 8192
  470. *
  471. * @param integer $length Length of data to read
  472. *
  473. * @return mixed
  474. *
  475. * @see http://php.net/manual/en/function.fread.php
  476. * @since 1.0
  477. * @throws \RuntimeException
  478. */
  479. public function read($length = 0)
  480. {
  481. if (!$this->filesize && !$length)
  482. {
  483. // Get the filesize
  484. $this->filesize();
  485. if (!$this->filesize)
  486. {
  487. // Set it to the biggest and then wait until eof
  488. $length = -1;
  489. }
  490. else
  491. {
  492. $length = $this->filesize;
  493. }
  494. }
  495. if (!$this->fh)
  496. {
  497. throw new \RuntimeException('File not open');
  498. }
  499. $retval = false;
  500. // Capture PHP errors
  501. $php_errormsg = 'Error Unknown';
  502. $track_errors = ini_get('track_errors');
  503. ini_set('track_errors', true);
  504. $remaining = $length;
  505. do
  506. {
  507. // Do chunked reads where relevant
  508. switch ($this->processingmethod)
  509. {
  510. case 'bz':
  511. $res = ($remaining > 0) ? bzread($this->fh, $remaining) : bzread($this->fh, $this->chunksize);
  512. break;
  513. case 'gz':
  514. $res = ($remaining > 0) ? gzread($this->fh, $remaining) : gzread($this->fh, $this->chunksize);
  515. break;
  516. case 'f':
  517. default:
  518. $res = ($remaining > 0) ? fread($this->fh, $remaining) : fread($this->fh, $this->chunksize);
  519. break;
  520. }
  521. if (!$res)
  522. {
  523. throw new \RuntimeException($php_errormsg);
  524. }
  525. else
  526. {
  527. if (!$retval)
  528. {
  529. $retval = '';
  530. }
  531. $retval .= $res;
  532. if (!$this->eof())
  533. {
  534. $len = strlen($res);
  535. $remaining -= $len;
  536. }
  537. else
  538. {
  539. // If it's the end of the file then we've nothing left to read; reset remaining and len
  540. $remaining = 0;
  541. $length = strlen($retval);
  542. }
  543. }
  544. }
  545. while ($remaining || !$length);
  546. // Restore error tracking to what it was before
  547. ini_set('track_errors', $track_errors);
  548. // Return the result
  549. return $retval;
  550. }
  551. /**
  552. * Seek the file
  553. *
  554. * Note: the return value is different to that of fseek
  555. *
  556. * @param integer $offset Offset to use when seeking.
  557. * @param integer $whence Seek mode to use.
  558. *
  559. * @return boolean True on success, false on failure
  560. *
  561. * @see http://php.net/manual/en/function.fseek.php
  562. * @since 1.0
  563. * @throws \RuntimeException
  564. */
  565. public function seek($offset, $whence = SEEK_SET)
  566. {
  567. if (!$this->fh)
  568. {
  569. throw new \RuntimeException('File not open');
  570. }
  571. // Capture PHP errors
  572. $php_errormsg = '';
  573. $track_errors = ini_get('track_errors');
  574. ini_set('track_errors', true);
  575. switch ($this->processingmethod)
  576. {
  577. case 'gz':
  578. $res = gzseek($this->fh, $offset, $whence);
  579. break;
  580. case 'bz':
  581. case 'f':
  582. default:
  583. $res = fseek($this->fh, $offset, $whence);
  584. break;
  585. }
  586. // Seek, interestingly, returns 0 on success or -1 on failure.
  587. if ($res == -1)
  588. {
  589. throw new \RuntimeException($php_errormsg);
  590. }
  591. // Restore error tracking to what it was before
  592. ini_set('track_errors', $track_errors);
  593. // Return the result
  594. return true;
  595. }
  596. /**
  597. * Returns the current position of the file read/write pointer.
  598. *
  599. * @return mixed
  600. *
  601. * @since 1.0
  602. * @throws \RuntimeException
  603. */
  604. public function tell()
  605. {
  606. if (!$this->fh)
  607. {
  608. throw new \RuntimeException('File not open');
  609. }
  610. // Capture PHP errors
  611. $php_errormsg = '';
  612. $track_errors = ini_get('track_errors');
  613. ini_set('track_errors', true);
  614. switch ($this->processingmethod)
  615. {
  616. case 'gz':
  617. $res = gztell($this->fh);
  618. break;
  619. case 'bz':
  620. case 'f':
  621. default:
  622. $res = ftell($this->fh);
  623. break;
  624. }
  625. // May return 0 so check if it's really false
  626. if ($res === false)
  627. {
  628. throw new \RuntimeException($php_errormsg);
  629. }
  630. // Restore error tracking to what it was before
  631. ini_set('track_errors', $track_errors);
  632. // Return the result
  633. return $res;
  634. }
  635. /**
  636. * File write
  637. *
  638. * Whilst this function accepts a reference, the underlying fwrite
  639. * will do a copy! This will roughly double the memory allocation for
  640. * any write you do. Specifying chunked will get around this by only
  641. * writing in specific chunk sizes. This defaults to 8192 which is a
  642. * sane number to use most of the time (change the default with
  643. * Stream::set('chunksize', newsize);)
  644. * Note: This doesn't support gzip/bzip2 writing like reading does
  645. *
  646. * @param string &$string Reference to the string to write.
  647. * @param integer $length Length of the string to write.
  648. * @param integer $chunk Size of chunks to write in.
  649. *
  650. * @return boolean
  651. *
  652. * @see http://php.net/manual/en/function.fwrite.php
  653. * @since 1.0
  654. * @throws \RuntimeException
  655. */
  656. public function write(&$string, $length = 0, $chunk = 0)
  657. {
  658. if (!$this->fh)
  659. {
  660. throw new \RuntimeException('File not open');
  661. }
  662. // If the length isn't set, set it to the length of the string.
  663. if (!$length)
  664. {
  665. $length = strlen($string);
  666. }
  667. // If the chunk isn't set, set it to the default.
  668. if (!$chunk)
  669. {
  670. $chunk = $this->chunksize;
  671. }
  672. $retval = true;
  673. // Capture PHP errors
  674. $php_errormsg = '';
  675. $track_errors = ini_get('track_errors');
  676. ini_set('track_errors', true);
  677. $remaining = $length;
  678. $start = 0;
  679. do
  680. {
  681. // If the amount remaining is greater than the chunk size, then use the chunk
  682. $amount = ($remaining > $chunk) ? $chunk : $remaining;
  683. $res = fwrite($this->fh, substr($string, $start), $amount);
  684. // Returns false on error or the number of bytes written
  685. if ($res === false)
  686. {
  687. // Returned error
  688. throw new \RuntimeException($php_errormsg);
  689. }
  690. elseif ($res === 0)
  691. {
  692. // Wrote nothing?
  693. throw new \RuntimeException('Warning: No data written');
  694. }
  695. else
  696. {
  697. // Wrote something
  698. $start += $amount;
  699. $remaining -= $res;
  700. }
  701. }
  702. while ($remaining);
  703. // Restore error tracking to what it was before.
  704. ini_set('track_errors', $track_errors);
  705. // Return the result
  706. return $retval;
  707. }
  708. /**
  709. * Chmod wrapper
  710. *
  711. * @param string $filename File name.
  712. * @param mixed $mode Mode to use.
  713. *
  714. * @return boolean
  715. *
  716. * @since 1.0
  717. * @throws \RuntimeException
  718. */
  719. public function chmod($filename = '', $mode = 0)
  720. {
  721. if (!$filename)
  722. {
  723. if (!isset($this->filename) || !$this->filename)
  724. {
  725. throw new \RuntimeException('Filename not set');
  726. }
  727. $filename = $this->filename;
  728. }
  729. // If no mode is set use the default
  730. if (!$mode)
  731. {
  732. $mode = $this->filemode;
  733. }
  734. // Capture PHP errors
  735. $php_errormsg = '';
  736. $track_errors = ini_get('track_errors');
  737. ini_set('track_errors', true);
  738. $sch = parse_url($filename, PHP_URL_SCHEME);
  739. // Scheme specific options; ftp's chmod support is fun.
  740. switch ($sch)
  741. {
  742. case 'ftp':
  743. case 'ftps':
  744. $res = Helper::ftpChmod($filename, $mode);
  745. break;
  746. default:
  747. $res = chmod($filename, $mode);
  748. break;
  749. }
  750. // Seek, interestingly, returns 0 on success or -1 on failure
  751. if (!$res)
  752. {
  753. throw new \RuntimeException($php_errormsg);
  754. }
  755. // Restore error tracking to what it was before.
  756. ini_set('track_errors', $track_errors);
  757. // Return the result
  758. return true;
  759. }
  760. /**
  761. * Get the stream metadata
  762. *
  763. * @return array header/metadata
  764. *
  765. * @see http://php.net/manual/en/function.stream-get-meta-data.php
  766. * @since 1.0
  767. * @throws \RuntimeException
  768. */
  769. public function get_meta_data()
  770. {
  771. if (!$this->fh)
  772. {
  773. throw new \RuntimeException('File not open');
  774. }
  775. return stream_get_meta_data($this->fh);
  776. }
  777. /**
  778. * Stream contexts
  779. * Builds the context from the array
  780. *
  781. * @return mixed
  782. *
  783. * @since 1.0
  784. */
  785. public function _buildContext()
  786. {
  787. // According to the manual this always works!
  788. if (count($this->contextOptions))
  789. {
  790. $this->context = @stream_context_create($this->contextOptions);
  791. }
  792. else
  793. {
  794. $this->context = null;
  795. }
  796. }
  797. /**
  798. * Updates the context to the array
  799. *
  800. * Format is the same as the options for stream_context_create
  801. *
  802. * @param array $context Options to create the context with
  803. *
  804. * @return void
  805. *
  806. * @see http://php.net/stream_context_create
  807. * @since 1.0
  808. */
  809. public function setContextOptions($context)
  810. {
  811. $this->contextOptions = $context;
  812. $this->_buildContext();
  813. }
  814. /**
  815. * Adds a particular options to the context
  816. *
  817. * @param string $wrapper The wrapper to use
  818. * @param string $name The option to set
  819. * @param string $value The value of the option
  820. *
  821. * @return void
  822. *
  823. * @see http://php.net/stream_context_create Stream Context Creation
  824. * @see http://php.net/manual/en/context.php Context Options for various streams
  825. * @since 1.0
  826. */
  827. public function addContextEntry($wrapper, $name, $value)
  828. {
  829. $this->contextOptions[$wrapper][$name] = $value;
  830. $this->_buildContext();
  831. }
  832. /**
  833. * Deletes a particular setting from a context
  834. *
  835. * @param string $wrapper The wrapper to use
  836. * @param string $name The option to unset
  837. *
  838. * @return void
  839. *
  840. * @see http://php.net/stream_context_create
  841. * @since 1.0
  842. */
  843. public function deleteContextEntry($wrapper, $name)
  844. {
  845. // Check whether the wrapper is set
  846. if (isset($this->contextOptions[$wrapper]))
  847. {
  848. // Check that entry is set for that wrapper
  849. if (isset($this->contextOptions[$wrapper][$name]))
  850. {
  851. // Unset the item
  852. unset($this->contextOptions[$wrapper][$name]);
  853. // Check that there are still items there
  854. if (!count($this->contextOptions[$wrapper]))
  855. {
  856. // Clean up an empty wrapper context option
  857. unset($this->contextOptions[$wrapper]);
  858. }
  859. }
  860. }
  861. // Rebuild the context and apply it to the stream
  862. $this->_buildContext();
  863. }
  864. /**
  865. * Applies the current context to the stream
  866. *
  867. * Use this to change the values of the context after you've opened a stream
  868. *
  869. * @return mixed
  870. *
  871. * @since 1.0
  872. * @throws \RuntimeException
  873. */
  874. public function applyContextToStream()
  875. {
  876. $retval = false;
  877. if ($this->fh)
  878. {
  879. // Capture PHP errors
  880. $php_errormsg = 'Unknown error setting context option';
  881. $track_errors = ini_get('track_errors');
  882. ini_set('track_errors', true);
  883. $retval = @stream_context_set_option($this->fh, $this->contextOptions);
  884. if (!$retval)
  885. {
  886. throw new \RuntimeException($php_errormsg);
  887. }
  888. // Restore error tracking to what it was before
  889. ini_set('track_errors', $track_errors);
  890. }
  891. return $retval;
  892. }
  893. /**
  894. * Stream filters
  895. * Append a filter to the chain
  896. *
  897. * @param string $filtername The key name of the filter.
  898. * @param integer $read_write Optional. Defaults to STREAM_FILTER_READ.
  899. * @param array $params An array of params for the stream_filter_append call.
  900. *
  901. * @return mixed
  902. *
  903. * @see http://php.net/manual/en/function.stream-filter-append.php
  904. * @since 1.0
  905. * @throws \RuntimeException
  906. */
  907. public function appendFilter($filtername, $read_write = STREAM_FILTER_READ, $params = array())
  908. {
  909. $res = false;
  910. if ($this->fh)
  911. {
  912. // Capture PHP errors
  913. $php_errormsg = '';
  914. $track_errors = ini_get('track_errors');
  915. ini_set('track_errors', true);
  916. $res = @stream_filter_append($this->fh, $filtername, $read_write, $params);
  917. if (!$res && $php_errormsg)
  918. {
  919. throw new \RuntimeException($php_errormsg);
  920. }
  921. else
  922. {
  923. $this->filters[] = &$res;
  924. }
  925. // Restore error tracking to what it was before.
  926. ini_set('track_errors', $track_errors);
  927. }
  928. return $res;
  929. }
  930. /**
  931. * Prepend a filter to the chain
  932. *
  933. * @param string $filtername The key name of the filter.
  934. * @param integer $read_write Optional. Defaults to STREAM_FILTER_READ.
  935. * @param array $params An array of params for the stream_filter_prepend call.
  936. *
  937. * @return mixed
  938. *
  939. * @see http://php.net/manual/en/function.stream-filter-prepend.php
  940. * @since 1.0
  941. * @throws \RuntimeException
  942. */
  943. public function prependFilter($filtername, $read_write = STREAM_FILTER_READ, $params = array())
  944. {
  945. $res = false;
  946. if ($this->fh)
  947. {
  948. // Capture PHP errors
  949. $php_errormsg = '';
  950. $track_errors = ini_get('track_errors');
  951. ini_set('track_errors', true);
  952. $res = @stream_filter_prepend($this->fh, $filtername, $read_write, $params);
  953. if (!$res && $php_errormsg)
  954. {
  955. // Set the error msg
  956. throw new \RuntimeException($php_errormsg);
  957. }
  958. else
  959. {
  960. array_unshift($res, '');
  961. $res[0] = &$this->filters;
  962. }
  963. // Restore error tracking to what it was before.
  964. ini_set('track_errors', $track_errors);
  965. }
  966. return $res;
  967. }
  968. /**
  969. * Remove a filter, either by resource (handed out from the append or prepend function)
  970. * or via getting the filter list)
  971. *
  972. * @param resource &$resource The resource.
  973. * @param boolean $byindex The index of the filter.
  974. *
  975. * @return boolean Result of operation
  976. *
  977. * @since 1.0
  978. * @throws \RuntimeException
  979. */
  980. public function removeFilter(&$resource, $byindex = false)
  981. {
  982. // Capture PHP errors
  983. $php_errormsg = '';
  984. $track_errors = ini_get('track_errors');
  985. ini_set('track_errors', true);
  986. if ($byindex)
  987. {
  988. $res = stream_filter_remove($this->filters[$resource]);
  989. }
  990. else
  991. {
  992. $res = stream_filter_remove($resource);
  993. }
  994. if ($res && $php_errormsg)
  995. {
  996. throw new \RuntimeException($php_errormsg);
  997. }
  998. // Restore error tracking to what it was before.
  999. ini_set('track_errors', $track_errors);
  1000. return $res;
  1001. }
  1002. /**
  1003. * Copy a file from src to dest
  1004. *
  1005. * @param string $src The file path to copy from.
  1006. * @param string $dest The file path to copy to.
  1007. * @param resource $context A valid context resource (optional) created with stream_context_create.
  1008. * @param boolean $use_prefix Controls the use of a prefix (optional).
  1009. * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
  1010. *
  1011. * @return mixed
  1012. *
  1013. * @since 1.0
  1014. * @throws \RuntimeException
  1015. */
  1016. public function copy($src, $dest, $context = null, $use_prefix = true, $relative = false)
  1017. {
  1018. // Capture PHP errors
  1019. $php_errormsg = '';
  1020. $track_errors = ini_get('track_errors');
  1021. ini_set('track_errors', true);
  1022. $chmodDest = $this->_getFilename($dest, 'w', $use_prefix, $relative);
  1023. // Since we're going to open the file directly we need to get the filename.
  1024. // We need to use the same prefix so force everything to write.
  1025. $src = $this->_getFilename($src, 'w', $use_prefix, $relative);
  1026. $dest = $this->_getFilename($dest, 'w', $use_prefix, $relative);
  1027. if ($context)
  1028. {
  1029. // Use the provided context
  1030. $res = @copy($src, $dest, $context);
  1031. }
  1032. elseif ($this->context)
  1033. {
  1034. // Use the objects context
  1035. $res = @copy($src, $dest, $this->context);
  1036. }
  1037. else
  1038. {
  1039. // Don't use any context
  1040. $res = @copy($src, $dest);
  1041. }
  1042. if (!$res && $php_errormsg)
  1043. {
  1044. throw new \RuntimeException($php_errormsg);
  1045. }
  1046. else
  1047. {
  1048. $this->chmod($chmodDest);
  1049. }
  1050. // Restore error tracking to what it was before
  1051. ini_set('track_errors', $track_errors);
  1052. return $res;
  1053. }
  1054. /**
  1055. * Moves a file
  1056. *
  1057. * @param string $src The file path to move from.
  1058. * @param string $dest The file path to move to.
  1059. * @param resource $context A valid context resource (optional) created with stream_context_create.
  1060. * @param boolean $use_prefix Controls the use of a prefix (optional).
  1061. * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
  1062. *
  1063. * @return mixed
  1064. *
  1065. * @since 1.0
  1066. * @throws \RuntimeException
  1067. */
  1068. public function move($src, $dest, $context = null, $use_prefix = true, $relative = false)
  1069. {
  1070. // Capture PHP errors
  1071. $php_errormsg = '';
  1072. $track_errors = ini_get('track_errors');
  1073. ini_set('track_errors', true);
  1074. $src = $this->_getFilename($src, 'w', $use_prefix, $relative);
  1075. $dest = $this->_getFilename($dest, 'w', $use_prefix, $relative);
  1076. if ($context)
  1077. {
  1078. // Use the provided context
  1079. $res = @rename($src, $dest, $context);
  1080. }
  1081. elseif ($this->context)
  1082. {
  1083. // Use the object's context
  1084. $res = @rename($src, $dest, $this->context);
  1085. }
  1086. else
  1087. {
  1088. // Don't use any context
  1089. $res = @rename($src, $dest);
  1090. }
  1091. if (!$res && $php_errormsg)
  1092. {
  1093. throw new \RuntimeException($php_errormsg());
  1094. }
  1095. $this->chmod($dest);
  1096. // Restore error tracking to what it was before
  1097. ini_set('track_errors', $track_errors);
  1098. return $res;
  1099. }
  1100. /**
  1101. * Delete a file
  1102. *
  1103. * @param string $filename The file path to delete.
  1104. * @param resource $context A valid context resource (optional) created with stream_context_create.
  1105. * @param boolean $use_prefix Controls the use of a prefix (optional).
  1106. * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
  1107. *
  1108. * @return mixed
  1109. *
  1110. * @since 1.0
  1111. * @throws \RuntimeException
  1112. */
  1113. public function delete($filename, $context = null, $use_prefix = true, $relative = false)
  1114. {
  1115. // Capture PHP errors
  1116. $php_errormsg = '';
  1117. $track_errors = ini_get('track_errors');
  1118. ini_set('track_errors', true);
  1119. $filename = $this->_getFilename($filename, 'w', $use_prefix, $relative);
  1120. if ($context)
  1121. {
  1122. // Use the provided context
  1123. $res = @unlink($filename, $context);
  1124. }
  1125. elseif ($this->context)
  1126. {
  1127. // Use the object's context
  1128. $res = @unlink($filename, $this->context);
  1129. }
  1130. else
  1131. {
  1132. // Don't use any context
  1133. $res = @unlink($filename);
  1134. }
  1135. if (!$res && $php_errormsg)
  1136. {
  1137. throw new \RuntimeException($php_errormsg());
  1138. }
  1139. // Restore error tracking to what it was before.
  1140. ini_set('track_errors', $track_errors);
  1141. return $res;
  1142. }
  1143. /**
  1144. * Upload a file
  1145. *
  1146. * @param string $src The file path to copy from (usually a temp folder).
  1147. * @param string $dest The file path to copy to.
  1148. * @param resource $context A valid context resource (optional) created with stream_context_create.
  1149. * @param boolean $use_prefix Controls the use of a prefix (optional).
  1150. * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
  1151. *
  1152. * @return mixed
  1153. *
  1154. * @since 1.0
  1155. * @throws \RuntimeException
  1156. */
  1157. public function upload($src, $dest, $context = null, $use_prefix = true, $relative = false)
  1158. {
  1159. if (is_uploaded_file($src))
  1160. {
  1161. // Make sure it's an uploaded file
  1162. return $this->copy($src, $dest, $context, $use_prefix, $relative);
  1163. }
  1164. else
  1165. {
  1166. throw new \RuntimeException('Not an uploaded file.');
  1167. }
  1168. }
  1169. /**
  1170. * Writes a chunk of data to a file.
  1171. *
  1172. * @param string $filename The file name.
  1173. * @param string &$buffer The data to write to the file.
  1174. *
  1175. * @return boolean
  1176. *
  1177. * @since 1.0
  1178. */
  1179. public function writeFile($filename, &$buffer)
  1180. {
  1181. if ($this->open($filename, 'w'))
  1182. {
  1183. $result = $this->write($buffer);
  1184. $this->chmod();
  1185. $this->close();
  1186. return $result;
  1187. }
  1188. return false;
  1189. }
  1190. /**
  1191. * Determine the appropriate 'filename' of a file
  1192. *
  1193. * @param string $filename Original filename of the file
  1194. * @param string $mode Mode string to retrieve the filename
  1195. * @param boolean $use_prefix Controls the use of a prefix
  1196. * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
  1197. *
  1198. * @return string
  1199. *
  1200. * @since 1.0
  1201. */
  1202. public function _getFilename($filename, $mode, $use_prefix, $relative)
  1203. {
  1204. if ($use_prefix)
  1205. {
  1206. // Get rid of binary or t, should be at the end of the string
  1207. $tmode = trim($mode, 'btf123456789');
  1208. // Check if it's a write mode then add the appropriate prefix
  1209. // Get rid of JPATH_ROOT (legacy compat) along the way
  1210. if (in_array($tmode, Helper::getWriteModes()))
  1211. {
  1212. if (!$relative && $this->writeprefix)
  1213. {
  1214. $filename = str_replace(JPATH_ROOT, '', $filename);
  1215. }
  1216. $filename = $this->writeprefix . $filename;
  1217. }
  1218. else
  1219. {
  1220. if (!$relative && $this->readprefix)
  1221. {
  1222. $filename = str_replace(JPATH_ROOT, '', $filename);
  1223. }
  1224. $filename = $this->readprefix . $filename;
  1225. }
  1226. }
  1227. return $filename;
  1228. }
  1229. /**
  1230. * Return the internal file handle
  1231. *
  1232. * @return File handler
  1233. *
  1234. * @since 1.0
  1235. */
  1236. public function getFileHandle()
  1237. {
  1238. return $this->fh;
  1239. }
  1240. /**
  1241. * Modifies a property of the object, creating it if it does not already exist.
  1242. *
  1243. * @param string $property The name of the property.
  1244. * @param mixed $value The value of the property to set.
  1245. *
  1246. * @return mixed Previous value of the property.
  1247. *
  1248. * @since 1.0
  1249. */
  1250. public function set($property, $value = null)
  1251. {
  1252. $previous = isset($this->$property) ? $this->$property : null;
  1253. $this->$property = $value;
  1254. return $previous;
  1255. }
  1256. /**
  1257. * Returns a property of the object or the default value if the property is not set.
  1258. *
  1259. * @param string $property The name of the property.
  1260. * @param mixed $default The default value.
  1261. *
  1262. * @return mixed The value of the property.
  1263. *
  1264. * @since 1.0
  1265. */
  1266. public function get($property, $default = null)
  1267. {
  1268. if (isset($this->$property))
  1269. {
  1270. return $this->$property;
  1271. }
  1272. return $default;
  1273. }
  1274. }