Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / mikey179 / vfsStream / src / main / php / org / bovigo / vfs / vfsStreamWrapper.php
1 <?php
2 /**
3  * This file is part of vfsStream.
4  *
5  * For the full copyright and license information, please view the LICENSE
6  * file that was distributed with this source code.
7  *
8  * @package  org\bovigo\vfs
9  */
10 namespace org\bovigo\vfs;
11 /**
12  * Stream wrapper to mock file system requests.
13  */
14 class vfsStreamWrapper
15 {
16     /**
17      * open file for reading
18      */
19     const READ                   = 'r';
20     /**
21      * truncate file
22      */
23     const TRUNCATE               = 'w';
24     /**
25      * set file pointer to end, append new data
26      */
27     const APPEND                 = 'a';
28     /**
29      * set file pointer to start, overwrite existing data
30      */
31     const WRITE                  = 'x';
32     /**
33      * set file pointer to start, overwrite existing data; or create file if
34      * does not exist
35      */
36     const WRITE_NEW              = 'c';
37     /**
38      * file mode: read only
39      */
40     const READONLY               = 0;
41     /**
42      * file mode: write only
43      */
44     const WRITEONLY              = 1;
45     /**
46      * file mode: read and write
47      */
48     const ALL                    = 2;
49     /**
50      * switch whether class has already been registered as stream wrapper or not
51      *
52      * @type  bool
53      */
54     protected static $registered = false;
55     /**
56      * root content
57      *
58      * @type  vfsStreamContent
59      */
60     protected static $root;
61     /**
62      * disk space quota
63      *
64      * @type  Quota
65      */
66     private static $quota;
67     /**
68      * file mode: read only, write only, all
69      *
70      * @type  int
71      */
72     protected $mode;
73     /**
74      * shortcut to file container
75      *
76      * @type  vfsStreamFile
77      */
78     protected $content;
79     /**
80      * shortcut to directory container
81      *
82      * @type  vfsStreamDirectory
83      */
84     protected $dir;
85     /**
86      * shortcut to directory container iterator
87      *
88      * @type  vfsStreamDirectory
89      */
90     protected $dirIterator;
91
92     /**
93      * method to register the stream wrapper
94      *
95      * Please be aware that a call to this method will reset the root element
96      * to null.
97      * If the stream is already registered the method returns silently. If there
98      * is already another stream wrapper registered for the scheme used by
99      * vfsStream a vfsStreamException will be thrown.
100      *
101      * @throws  vfsStreamException
102      */
103     public static function register()
104     {
105         self::$root  = null;
106         self::$quota = Quota::unlimited();
107         if (true === self::$registered) {
108             return;
109         }
110
111         if (@stream_wrapper_register(vfsStream::SCHEME, __CLASS__) === false) {
112             throw new vfsStreamException('A handler has already been registered for the ' . vfsStream::SCHEME . ' protocol.');
113         }
114
115         self::$registered = true;
116     }
117
118     /**
119      * Unregisters a previously registered URL wrapper for the vfs scheme.
120      *
121      * If this stream wrapper wasn't registered, the method returns silently.
122      *
123      * If unregistering fails, or if the URL wrapper for vfs:// was not
124      * registered with this class, a vfsStreamException will be thrown.
125      *
126      * @throws vfsStreamException
127      * @since  1.6.0
128      */
129     public static function unregister()
130     {
131         if (!self::$registered) {
132             if (in_array(vfsStream::SCHEME, stream_get_wrappers())) {
133                 throw new vfsStreamException('The URL wrapper for the protocol ' . vfsStream::SCHEME . ' was not registered with this version of vfsStream.');
134             }
135             return;
136         }
137
138         if (!@stream_wrapper_unregister(vfsStream::SCHEME)) {
139             throw new vfsStreamException('Failed to unregister the URL wrapper for the ' . vfsStream::SCHEME . ' protocol.');
140         }
141
142         self::$registered = false;
143     }
144
145     /**
146      * sets the root content
147      *
148      * @param   vfsStreamContainer  $root
149      * @return  vfsStreamContainer
150      */
151     public static function setRoot(vfsStreamContainer $root)
152     {
153         self::$root = $root;
154         clearstatcache();
155         return self::$root;
156     }
157
158     /**
159      * returns the root content
160      *
161      * @return  vfsStreamContainer
162      */
163     public static function getRoot()
164     {
165         return self::$root;
166     }
167
168     /**
169      * sets quota for disk space
170      *
171      * @param  Quota  $quota
172      * @since  1.1.0
173      */
174     public static function setQuota(Quota $quota)
175     {
176         self::$quota = $quota;
177     }
178
179     /**
180      * returns content for given path
181      *
182      * @param   string  $path
183      * @return  vfsStreamContent
184      */
185     protected function getContent($path)
186     {
187         if (null === self::$root) {
188             return null;
189         }
190
191         if (self::$root->getName() === $path) {
192             return self::$root;
193         }
194
195         if ($this->isInRoot($path) && self::$root->hasChild($path) === true) {
196             return self::$root->getChild($path);
197         }
198
199         return null;
200     }
201
202     /**
203      * helper method to detect whether given path is in root path
204      *
205      * @param   string  $path
206      * @return  bool
207      */
208     private function isInRoot($path)
209     {
210         return substr($path, 0, strlen(self::$root->getName())) === self::$root->getName();
211     }
212
213     /**
214      * returns content for given path but only when it is of given type
215      *
216      * @param   string  $path
217      * @param   int     $type
218      * @return  vfsStreamContent
219      */
220     protected function getContentOfType($path, $type)
221     {
222         $content = $this->getContent($path);
223         if (null !== $content && $content->getType() === $type) {
224             return $content;
225         }
226
227         return null;
228     }
229
230     /**
231      * splits path into its dirname and the basename
232      *
233      * @param   string  $path
234      * @return  string[]
235      */
236     protected function splitPath($path)
237     {
238         $lastSlashPos = strrpos($path, '/');
239         if (false === $lastSlashPos) {
240             return array('dirname' => '', 'basename' => $path);
241         }
242
243         return array('dirname'  => substr($path, 0, $lastSlashPos),
244                      'basename' => substr($path, $lastSlashPos + 1)
245                );
246     }
247
248     /**
249      * helper method to resolve a path from /foo/bar/. to /foo/bar
250      *
251      * @param   string  $path
252      * @return  string
253      */
254     protected function resolvePath($path)
255     {
256         $newPath  = array();
257         foreach (explode('/', $path) as $pathPart) {
258             if ('.' !== $pathPart) {
259                 if ('..' !== $pathPart) {
260                     $newPath[] = $pathPart;
261                 } elseif (count($newPath) > 1) {
262                     array_pop($newPath);
263                 }
264             }
265         }
266
267         return implode('/', $newPath);
268     }
269
270     /**
271      * open the stream
272      *
273      * @param   string  $path         the path to open
274      * @param   string  $mode         mode for opening
275      * @param   string  $options      options for opening
276      * @param   string  $opened_path  full path that was actually opened
277      * @return  bool
278      */
279     public function stream_open($path, $mode, $options, $opened_path)
280     {
281         $extended = ((strstr($mode, '+') !== false) ? (true) : (false));
282         $mode     = str_replace(array('t', 'b', '+'), '', $mode);
283         if (in_array($mode, array('r', 'w', 'a', 'x', 'c')) === false) {
284             if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
285                 trigger_error('Illegal mode ' . $mode . ', use r, w, a, x  or c, flavoured with t, b and/or +', E_USER_WARNING);
286             }
287
288             return false;
289         }
290
291         $this->mode    = $this->calculateMode($mode, $extended);
292         $path          = $this->resolvePath(vfsStream::path($path));
293         $this->content = $this->getContentOfType($path, vfsStreamContent::TYPE_FILE);
294         if (null !== $this->content) {
295             if (self::WRITE === $mode) {
296                 if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
297                     trigger_error('File ' . $path . ' already exists, can not open with mode x', E_USER_WARNING);
298                 }
299
300                 return false;
301             }
302
303             if (
304                 (self::TRUNCATE === $mode || self::APPEND === $mode) &&
305                 $this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false
306             ) {
307                 return false;
308             }
309
310             if (self::TRUNCATE === $mode) {
311                 $this->content->openWithTruncate();
312             } elseif (self::APPEND === $mode) {
313                 $this->content->openForAppend();
314             } else {
315                 if (!$this->content->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) {
316                     if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
317                         trigger_error('Permission denied', E_USER_WARNING);
318                     }
319                     return false;
320                 }
321                 $this->content->open();
322             }
323
324             return true;
325         }
326
327         $content = $this->createFile($path, $mode, $options);
328         if (false === $content) {
329             return false;
330         }
331
332         $this->content = $content;
333         return true;
334     }
335
336     /**
337      * creates a file at given path
338      *
339      * @param   string  $path     the path to open
340      * @param   string  $mode     mode for opening
341      * @param   string  $options  options for opening
342      * @return  bool
343      */
344     private function createFile($path, $mode = null, $options = null)
345     {
346         $names = $this->splitPath($path);
347         if (empty($names['dirname']) === true) {
348             if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
349                 trigger_error('File ' . $names['basename'] . ' does not exist', E_USER_WARNING);
350             }
351
352             return false;
353         }
354
355         $dir = $this->getContentOfType($names['dirname'], vfsStreamContent::TYPE_DIR);
356         if (null === $dir) {
357             if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
358                 trigger_error('Directory ' . $names['dirname'] . ' does not exist', E_USER_WARNING);
359             }
360
361             return false;
362         } elseif ($dir->hasChild($names['basename']) === true) {
363             if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
364                 trigger_error('Directory ' . $names['dirname'] . ' already contains a director named ' . $names['basename'], E_USER_WARNING);
365             }
366
367             return false;
368         }
369
370         if (self::READ === $mode) {
371             if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
372                 trigger_error('Can not open non-existing file ' . $path . ' for reading', E_USER_WARNING);
373             }
374
375             return false;
376         }
377
378         if ($dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
379             if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
380                 trigger_error('Can not create new file in non-writable path ' . $names['dirname'], E_USER_WARNING);
381             }
382
383             return false;
384         }
385
386         return vfsStream::newFile($names['basename'])->at($dir);
387     }
388
389     /**
390      * calculates the file mode
391      *
392      * @param   string  $mode      opening mode: r, w, a or x
393      * @param   bool    $extended  true if + was set with opening mode
394      * @return  int
395      */
396     protected function calculateMode($mode, $extended)
397     {
398         if (true === $extended) {
399             return self::ALL;
400         }
401
402         if (self::READ === $mode) {
403             return self::READONLY;
404         }
405
406         return self::WRITEONLY;
407     }
408
409     /**
410      * closes the stream
411      *
412      * @see     https://github.com/mikey179/vfsStream/issues/40
413      */
414     public function stream_close()
415     {
416         $this->content->lock($this, LOCK_UN);
417     }
418
419     /**
420      * read the stream up to $count bytes
421      *
422      * @param   int     $count  amount of bytes to read
423      * @return  string
424      */
425     public function stream_read($count)
426     {
427         if (self::WRITEONLY === $this->mode) {
428             return '';
429         }
430
431         if ($this->content->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
432             return '';
433         }
434
435         return $this->content->read($count);
436     }
437
438     /**
439      * writes data into the stream
440      *
441      * @param   string  $data
442      * @return  int     amount of bytes written
443      */
444     public function stream_write($data)
445     {
446         if (self::READONLY === $this->mode) {
447             return 0;
448         }
449
450         if ($this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
451             return 0;
452         }
453
454         if (self::$quota->isLimited()) {
455             $data = substr($data, 0, self::$quota->spaceLeft(self::$root->sizeSummarized()));
456         }
457
458         return $this->content->write($data);
459     }
460
461     /**
462      * truncates a file to a given length
463      *
464      * @param   int  $size  length to truncate file to
465      * @return  bool
466      * @since   1.1.0
467      */
468     public function stream_truncate($size)
469     {
470         if (self::READONLY === $this->mode) {
471             return false;
472         }
473
474         if ($this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
475             return false;
476         }
477
478         if ($this->content->getType() !== vfsStreamContent::TYPE_FILE) {
479             return false;
480         }
481
482         if (self::$quota->isLimited() && $this->content->size() < $size) {
483             $maxSize = self::$quota->spaceLeft(self::$root->sizeSummarized());
484             if (0 === $maxSize) {
485                 return false;
486             }
487
488             if ($size > $maxSize) {
489                 $size = $maxSize;
490             }
491         }
492
493         return $this->content->truncate($size);
494     }
495
496     /**
497      * sets metadata like owner, user or permissions
498      *
499      * @param   string  $path
500      * @param   int     $option
501      * @param   mixed   $var
502      * @return  bool
503      * @since   1.1.0
504      */
505     public function stream_metadata($path, $option, $var)
506     {
507         $path    = $this->resolvePath(vfsStream::path($path));
508         $content = $this->getContent($path);
509         switch ($option) {
510             case STREAM_META_TOUCH:
511                 if (null === $content) {
512                     $content = $this->createFile($path, null, STREAM_REPORT_ERRORS);
513                     // file creation may not be allowed at provided path
514                     if (false === $content) {
515                         return false;
516                     }
517                 }
518
519                 $currentTime = time();
520                 $content->lastModified(((isset($var[0])) ? ($var[0]) : ($currentTime)))
521                         ->lastAccessed(((isset($var[1])) ? ($var[1]) : ($currentTime)));
522                 return true;
523
524             case STREAM_META_OWNER_NAME:
525                 return false;
526
527             case STREAM_META_OWNER:
528                 if (null === $content) {
529                     return false;
530                 }
531
532                 return $this->doPermChange($path,
533                                            $content,
534                                            function() use ($content, $var)
535                                            {
536                                                $content->chown($var);
537                                            }
538                 );
539
540             case STREAM_META_GROUP_NAME:
541                 return false;
542
543             case STREAM_META_GROUP:
544                 if (null === $content) {
545                     return false;
546                 }
547
548                 return $this->doPermChange($path,
549                                            $content,
550                                            function() use ($content, $var)
551                                            {
552                                                $content->chgrp($var);
553                                            }
554                 );
555
556             case STREAM_META_ACCESS:
557                 if (null === $content) {
558                     return false;
559                 }
560
561                 return $this->doPermChange($path,
562                                            $content,
563                                            function() use ($content, $var)
564                                            {
565                                                $content->chmod($var);
566                                            }
567                 );
568
569             default:
570                 return false;
571         }
572     }
573
574     /**
575      * executes given permission change when necessary rights allow such a change
576      *
577      * @param   string                    $path
578      * @param   vfsStreamAbstractContent  $content
579      * @param   \Closure                  $change
580      * @return  bool
581      */
582     private function doPermChange($path, vfsStreamAbstractContent $content, \Closure $change)
583     {
584         if (!$content->isOwnedByUser(vfsStream::getCurrentUser())) {
585             return false;
586         }
587
588         if (self::$root->getName() !== $path) {
589             $names   = $this->splitPath($path);
590             $parent = $this->getContent($names['dirname']);
591             if (!$parent->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) {
592                 return false;
593             }
594         }
595
596         $change();
597         return true;
598     }
599
600     /**
601      * checks whether stream is at end of file
602      *
603      * @return  bool
604      */
605     public function stream_eof()
606     {
607         return $this->content->eof();
608     }
609
610     /**
611      * returns the current position of the stream
612      *
613      * @return  int
614      */
615     public function stream_tell()
616     {
617         return $this->content->getBytesRead();
618     }
619
620     /**
621      * seeks to the given offset
622      *
623      * @param   int   $offset
624      * @param   int   $whence
625      * @return  bool
626      */
627     public function stream_seek($offset, $whence)
628     {
629         return $this->content->seek($offset, $whence);
630     }
631
632     /**
633      * flushes unstored data into storage
634      *
635      * @return  bool
636      */
637     public function stream_flush()
638     {
639         return true;
640     }
641
642     /**
643      * returns status of stream
644      *
645      * @return  array
646      */
647     public function stream_stat()
648     {
649         $fileStat = array('dev'     => 0,
650                           'ino'     => 0,
651                           'mode'    => $this->content->getType() | $this->content->getPermissions(),
652                           'nlink'   => 0,
653                           'uid'     => $this->content->getUser(),
654                           'gid'     => $this->content->getGroup(),
655                           'rdev'    => 0,
656                           'size'    => $this->content->size(),
657                           'atime'   => $this->content->fileatime(),
658                           'mtime'   => $this->content->filemtime(),
659                           'ctime'   => $this->content->filectime(),
660                           'blksize' => -1,
661                           'blocks'  => -1
662                     );
663         return array_merge(array_values($fileStat), $fileStat);
664     }
665
666     /**
667      * retrieve the underlaying resource
668      *
669      * Please note that this method always returns false as there is no
670      * underlaying resource to return.
671      *
672      * @param   int  $cast_as
673      * @since   0.9.0
674      * @see     https://github.com/mikey179/vfsStream/issues/3
675      * @return  bool
676      */
677     public function stream_cast($cast_as)
678     {
679         return false;
680     }
681
682     /**
683      * set lock status for stream
684      *
685      * @param   int   $operation
686      * @return  bool
687      * @since   0.10.0
688      * @see     https://github.com/mikey179/vfsStream/issues/6
689      * @see     https://github.com/mikey179/vfsStream/issues/31
690      * @see     https://github.com/mikey179/vfsStream/issues/40
691      */
692     public function stream_lock($operation)
693     {
694         if ((LOCK_NB & $operation) == LOCK_NB) {
695             $operation = $operation - LOCK_NB;
696         }
697
698         return $this->content->lock($this, $operation);
699     }
700
701     /**
702      * sets options on the stream
703      *
704      * @param   int   $option  key of option to set
705      * @param   int   $arg1
706      * @param   int   $arg2
707      * @return  bool
708      * @since   0.10.0
709      * @see     https://github.com/mikey179/vfsStream/issues/15
710      * @see     http://www.php.net/manual/streamwrapper.stream-set-option.php
711      */
712     public function stream_set_option($option, $arg1, $arg2)
713     {
714         switch ($option) {
715             case STREAM_OPTION_BLOCKING:
716                 // break omitted
717
718             case STREAM_OPTION_READ_TIMEOUT:
719                 // break omitted
720
721             case STREAM_OPTION_WRITE_BUFFER:
722                 // break omitted
723
724             default:
725                 // nothing to do here
726         }
727
728         return false;
729     }
730
731     /**
732      * remove the data under the given path
733      *
734      * @param   string  $path
735      * @return  bool
736      */
737     public function unlink($path)
738     {
739         $realPath = $this->resolvePath(vfsStream::path($path));
740         $content  = $this->getContent($realPath);
741         if (null === $content) {
742             trigger_error('unlink(' . $path . '): No such file or directory', E_USER_WARNING);
743             return false;
744         }
745
746         if ($content->getType() !== vfsStreamContent::TYPE_FILE) {
747             trigger_error('unlink(' . $path . '): Operation not permitted', E_USER_WARNING);
748             return false;
749         }
750
751         return $this->doUnlink($realPath);
752     }
753
754     /**
755      * removes a path
756      *
757      * @param   string  $path
758      * @return  bool
759      */
760     protected function doUnlink($path)
761     {
762         if (self::$root->getName() === $path) {
763             // delete root? very brave. :)
764             self::$root = null;
765             clearstatcache();
766             return true;
767         }
768
769         $names   = $this->splitPath($path);
770         $content = $this->getContent($names['dirname']);
771         if (!$content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) {
772             return false;
773         }
774
775         clearstatcache();
776         return $content->removeChild($names['basename']);
777     }
778
779     /**
780      * rename from one path to another
781      *
782      * @param   string  $path_from
783      * @param   string  $path_to
784      * @return  bool
785      * @author  Benoit Aubuchon
786      */
787     public function rename($path_from, $path_to)
788     {
789         $srcRealPath = $this->resolvePath(vfsStream::path($path_from));
790         $dstRealPath = $this->resolvePath(vfsStream::path($path_to));
791         $srcContent  = $this->getContent($srcRealPath);
792         if (null == $srcContent) {
793             trigger_error(' No such file or directory', E_USER_WARNING);
794             return false;
795         }
796         $dstNames = $this->splitPath($dstRealPath);
797         $dstParentContent = $this->getContent($dstNames['dirname']);
798         if (null == $dstParentContent) {
799             trigger_error('No such file or directory', E_USER_WARNING);
800             return false;
801         }
802         if (!$dstParentContent->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) {
803             trigger_error('Permission denied', E_USER_WARNING);
804             return false;
805         }
806         if ($dstParentContent->getType() !== vfsStreamContent::TYPE_DIR) {
807             trigger_error('Target is not a directory', E_USER_WARNING);
808             return false;
809         }
810
811         // remove old source first, so we can rename later
812         // (renaming first would lead to not being able to remove the old path)
813         if (!$this->doUnlink($srcRealPath)) {
814             return false;
815         }
816
817         $dstContent = $srcContent;
818         // Renaming the filename
819         $dstContent->rename($dstNames['basename']);
820         // Copying to the destination
821         $dstParentContent->addChild($dstContent);
822         return true;
823     }
824
825     /**
826      * creates a new directory
827      *
828      * @param   string  $path
829      * @param   int     $mode
830      * @param   int     $options
831      * @return  bool
832      */
833     public function mkdir($path, $mode, $options)
834     {
835         $umask = vfsStream::umask();
836         if (0 < $umask) {
837             $permissions = $mode & ~$umask;
838         } else {
839             $permissions = $mode;
840         }
841
842         $path = $this->resolvePath(vfsStream::path($path));
843         if (null !== $this->getContent($path)) {
844             trigger_error('mkdir(): Path vfs://' . $path . ' exists', E_USER_WARNING);
845             return false;
846         }
847
848         if (null === self::$root) {
849             self::$root = vfsStream::newDirectory($path, $permissions);
850             return true;
851         }
852
853         $maxDepth = count(explode('/', $path));
854         $names    = $this->splitPath($path);
855         $newDirs  = $names['basename'];
856         $dir      = null;
857         $i        = 0;
858         while ($dir === null && $i < $maxDepth) {
859             $dir     = $this->getContent($names['dirname']);
860             $names   = $this->splitPath($names['dirname']);
861             if (null == $dir) {
862                 $newDirs = $names['basename'] . '/' . $newDirs;
863             }
864
865             $i++;
866         }
867
868         if (null === $dir
869           || $dir->getType() !== vfsStreamContent::TYPE_DIR
870           || $dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
871             return false;
872         }
873
874         $recursive = ((STREAM_MKDIR_RECURSIVE & $options) !== 0) ? (true) : (false);
875         if (strpos($newDirs, '/') !== false && false === $recursive) {
876             return false;
877         }
878
879         vfsStream::newDirectory($newDirs, $permissions)->at($dir);
880         return true;
881     }
882
883     /**
884      * removes a directory
885      *
886      * @param   string  $path
887      * @param   int     $options
888      * @return  bool
889      * @todo    consider $options with STREAM_MKDIR_RECURSIVE
890      */
891     public function rmdir($path, $options)
892     {
893         $path  = $this->resolvePath(vfsStream::path($path));
894         $child = $this->getContentOfType($path, vfsStreamContent::TYPE_DIR);
895         if (null === $child) {
896             return false;
897         }
898
899         // can only remove empty directories
900         if (count($child->getChildren()) > 0) {
901             return false;
902         }
903
904         if (self::$root->getName() === $path) {
905             // delete root? very brave. :)
906             self::$root = null;
907             clearstatcache();
908             return true;
909         }
910
911         $names = $this->splitPath($path);
912         $dir   = $this->getContentOfType($names['dirname'], vfsStreamContent::TYPE_DIR);
913         if ($dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
914             return false;
915         }
916
917         clearstatcache();
918         return $dir->removeChild($child->getName());
919     }
920
921     /**
922      * opens a directory
923      *
924      * @param   string  $path
925      * @param   int     $options
926      * @return  bool
927      */
928     public function dir_opendir($path, $options)
929     {
930         $path      = $this->resolvePath(vfsStream::path($path));
931         $this->dir = $this->getContentOfType($path, vfsStreamContent::TYPE_DIR);
932         if (null === $this->dir || $this->dir->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
933             return false;
934         }
935
936         $this->dirIterator = $this->dir->getIterator();
937         return true;
938     }
939
940     /**
941      * reads directory contents
942      *
943      * @return  string
944      */
945     public function dir_readdir()
946     {
947         $dir = $this->dirIterator->current();
948         if (null === $dir) {
949             return false;
950         }
951
952         $this->dirIterator->next();
953         return $dir->getName();
954     }
955
956     /**
957      * reset directory iteration
958      *
959      * @return  bool
960      */
961     public function dir_rewinddir()
962     {
963         return $this->dirIterator->rewind();
964     }
965
966     /**
967      * closes directory
968      *
969      * @return  bool
970      */
971     public function dir_closedir()
972     {
973         $this->dirIterator = null;
974         return true;
975     }
976
977     /**
978      * returns status of url
979      *
980      * @param   string  $path   path of url to return status for
981      * @param   int     $flags  flags set by the stream API
982      * @return  array
983      */
984     public function url_stat($path, $flags)
985     {
986         $content = $this->getContent($this->resolvePath(vfsStream::path($path)));
987         if (null === $content) {
988             if (($flags & STREAM_URL_STAT_QUIET) != STREAM_URL_STAT_QUIET) {
989                 trigger_error(' No such file or directory: ' . $path, E_USER_WARNING);
990             }
991
992             return false;
993
994         }
995
996         $fileStat = array('dev'     => 0,
997                           'ino'     => 0,
998                           'mode'    => $content->getType() | $content->getPermissions(),
999                           'nlink'   => 0,
1000                           'uid'     => $content->getUser(),
1001                           'gid'     => $content->getGroup(),
1002                           'rdev'    => 0,
1003                           'size'    => $content->size(),
1004                           'atime'   => $content->fileatime(),
1005                           'mtime'   => $content->filemtime(),
1006                           'ctime'   => $content->filectime(),
1007                           'blksize' => -1,
1008                           'blocks'  => -1
1009                     );
1010         return array_merge(array_values($fileStat), $fileStat);
1011     }
1012 }