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