--- /dev/null
+<?php
+/**
+ * This file is part of vfsStream.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package org\bovigo\vfs
+ */
+namespace org\bovigo\vfs;
+/**
+ * Stream wrapper to mock file system requests.
+ */
+class vfsStreamWrapper
+{
+ /**
+ * open file for reading
+ */
+ const READ = 'r';
+ /**
+ * truncate file
+ */
+ const TRUNCATE = 'w';
+ /**
+ * set file pointer to end, append new data
+ */
+ const APPEND = 'a';
+ /**
+ * set file pointer to start, overwrite existing data
+ */
+ const WRITE = 'x';
+ /**
+ * set file pointer to start, overwrite existing data; or create file if
+ * does not exist
+ */
+ const WRITE_NEW = 'c';
+ /**
+ * file mode: read only
+ */
+ const READONLY = 0;
+ /**
+ * file mode: write only
+ */
+ const WRITEONLY = 1;
+ /**
+ * file mode: read and write
+ */
+ const ALL = 2;
+ /**
+ * switch whether class has already been registered as stream wrapper or not
+ *
+ * @type bool
+ */
+ protected static $registered = false;
+ /**
+ * root content
+ *
+ * @type vfsStreamContent
+ */
+ protected static $root;
+ /**
+ * disk space quota
+ *
+ * @type Quota
+ */
+ private static $quota;
+ /**
+ * file mode: read only, write only, all
+ *
+ * @type int
+ */
+ protected $mode;
+ /**
+ * shortcut to file container
+ *
+ * @type vfsStreamFile
+ */
+ protected $content;
+ /**
+ * shortcut to directory container
+ *
+ * @type vfsStreamDirectory
+ */
+ protected $dir;
+ /**
+ * shortcut to directory container iterator
+ *
+ * @type vfsStreamDirectory
+ */
+ protected $dirIterator;
+
+ /**
+ * method to register the stream wrapper
+ *
+ * Please be aware that a call to this method will reset the root element
+ * to null.
+ * If the stream is already registered the method returns silently. If there
+ * is already another stream wrapper registered for the scheme used by
+ * vfsStream a vfsStreamException will be thrown.
+ *
+ * @throws vfsStreamException
+ */
+ public static function register()
+ {
+ self::$root = null;
+ self::$quota = Quota::unlimited();
+ if (true === self::$registered) {
+ return;
+ }
+
+ if (@stream_wrapper_register(vfsStream::SCHEME, __CLASS__) === false) {
+ throw new vfsStreamException('A handler has already been registered for the ' . vfsStream::SCHEME . ' protocol.');
+ }
+
+ self::$registered = true;
+ }
+
+ /**
+ * Unregisters a previously registered URL wrapper for the vfs scheme.
+ *
+ * If this stream wrapper wasn't registered, the method returns silently.
+ *
+ * If unregistering fails, or if the URL wrapper for vfs:// was not
+ * registered with this class, a vfsStreamException will be thrown.
+ *
+ * @throws vfsStreamException
+ * @since 1.6.0
+ */
+ public static function unregister()
+ {
+ if (!self::$registered) {
+ if (in_array(vfsStream::SCHEME, stream_get_wrappers())) {
+ throw new vfsStreamException('The URL wrapper for the protocol ' . vfsStream::SCHEME . ' was not registered with this version of vfsStream.');
+ }
+ return;
+ }
+
+ if (!@stream_wrapper_unregister(vfsStream::SCHEME)) {
+ throw new vfsStreamException('Failed to unregister the URL wrapper for the ' . vfsStream::SCHEME . ' protocol.');
+ }
+
+ self::$registered = false;
+ }
+
+ /**
+ * sets the root content
+ *
+ * @param vfsStreamContainer $root
+ * @return vfsStreamContainer
+ */
+ public static function setRoot(vfsStreamContainer $root)
+ {
+ self::$root = $root;
+ clearstatcache();
+ return self::$root;
+ }
+
+ /**
+ * returns the root content
+ *
+ * @return vfsStreamContainer
+ */
+ public static function getRoot()
+ {
+ return self::$root;
+ }
+
+ /**
+ * sets quota for disk space
+ *
+ * @param Quota $quota
+ * @since 1.1.0
+ */
+ public static function setQuota(Quota $quota)
+ {
+ self::$quota = $quota;
+ }
+
+ /**
+ * returns content for given path
+ *
+ * @param string $path
+ * @return vfsStreamContent
+ */
+ protected function getContent($path)
+ {
+ if (null === self::$root) {
+ return null;
+ }
+
+ if (self::$root->getName() === $path) {
+ return self::$root;
+ }
+
+ if ($this->isInRoot($path) && self::$root->hasChild($path) === true) {
+ return self::$root->getChild($path);
+ }
+
+ return null;
+ }
+
+ /**
+ * helper method to detect whether given path is in root path
+ *
+ * @param string $path
+ * @return bool
+ */
+ private function isInRoot($path)
+ {
+ return substr($path, 0, strlen(self::$root->getName())) === self::$root->getName();
+ }
+
+ /**
+ * returns content for given path but only when it is of given type
+ *
+ * @param string $path
+ * @param int $type
+ * @return vfsStreamContent
+ */
+ protected function getContentOfType($path, $type)
+ {
+ $content = $this->getContent($path);
+ if (null !== $content && $content->getType() === $type) {
+ return $content;
+ }
+
+ return null;
+ }
+
+ /**
+ * splits path into its dirname and the basename
+ *
+ * @param string $path
+ * @return string[]
+ */
+ protected function splitPath($path)
+ {
+ $lastSlashPos = strrpos($path, '/');
+ if (false === $lastSlashPos) {
+ return array('dirname' => '', 'basename' => $path);
+ }
+
+ return array('dirname' => substr($path, 0, $lastSlashPos),
+ 'basename' => substr($path, $lastSlashPos + 1)
+ );
+ }
+
+ /**
+ * helper method to resolve a path from /foo/bar/. to /foo/bar
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function resolvePath($path)
+ {
+ $newPath = array();
+ foreach (explode('/', $path) as $pathPart) {
+ if ('.' !== $pathPart) {
+ if ('..' !== $pathPart) {
+ $newPath[] = $pathPart;
+ } elseif (count($newPath) > 1) {
+ array_pop($newPath);
+ }
+ }
+ }
+
+ return implode('/', $newPath);
+ }
+
+ /**
+ * open the stream
+ *
+ * @param string $path the path to open
+ * @param string $mode mode for opening
+ * @param string $options options for opening
+ * @param string $opened_path full path that was actually opened
+ * @return bool
+ */
+ public function stream_open($path, $mode, $options, $opened_path)
+ {
+ $extended = ((strstr($mode, '+') !== false) ? (true) : (false));
+ $mode = str_replace(array('t', 'b', '+'), '', $mode);
+ if (in_array($mode, array('r', 'w', 'a', 'x', 'c')) === false) {
+ if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
+ trigger_error('Illegal mode ' . $mode . ', use r, w, a, x or c, flavoured with t, b and/or +', E_USER_WARNING);
+ }
+
+ return false;
+ }
+
+ $this->mode = $this->calculateMode($mode, $extended);
+ $path = $this->resolvePath(vfsStream::path($path));
+ $this->content = $this->getContentOfType($path, vfsStreamContent::TYPE_FILE);
+ if (null !== $this->content) {
+ if (self::WRITE === $mode) {
+ if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
+ trigger_error('File ' . $path . ' already exists, can not open with mode x', E_USER_WARNING);
+ }
+
+ return false;
+ }
+
+ if (
+ (self::TRUNCATE === $mode || self::APPEND === $mode) &&
+ $this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false
+ ) {
+ return false;
+ }
+
+ if (self::TRUNCATE === $mode) {
+ $this->content->openWithTruncate();
+ } elseif (self::APPEND === $mode) {
+ $this->content->openForAppend();
+ } else {
+ if (!$this->content->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) {
+ if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
+ trigger_error('Permission denied', E_USER_WARNING);
+ }
+ return false;
+ }
+ $this->content->open();
+ }
+
+ return true;
+ }
+
+ $content = $this->createFile($path, $mode, $options);
+ if (false === $content) {
+ return false;
+ }
+
+ $this->content = $content;
+ return true;
+ }
+
+ /**
+ * creates a file at given path
+ *
+ * @param string $path the path to open
+ * @param string $mode mode for opening
+ * @param string $options options for opening
+ * @return bool
+ */
+ private function createFile($path, $mode = null, $options = null)
+ {
+ $names = $this->splitPath($path);
+ if (empty($names['dirname']) === true) {
+ if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
+ trigger_error('File ' . $names['basename'] . ' does not exist', E_USER_WARNING);
+ }
+
+ return false;
+ }
+
+ $dir = $this->getContentOfType($names['dirname'], vfsStreamContent::TYPE_DIR);
+ if (null === $dir) {
+ if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
+ trigger_error('Directory ' . $names['dirname'] . ' does not exist', E_USER_WARNING);
+ }
+
+ return false;
+ } elseif ($dir->hasChild($names['basename']) === true) {
+ if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
+ trigger_error('Directory ' . $names['dirname'] . ' already contains a director named ' . $names['basename'], E_USER_WARNING);
+ }
+
+ return false;
+ }
+
+ if (self::READ === $mode) {
+ if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
+ trigger_error('Can not open non-existing file ' . $path . ' for reading', E_USER_WARNING);
+ }
+
+ return false;
+ }
+
+ if ($dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
+ if (($options & STREAM_REPORT_ERRORS) === STREAM_REPORT_ERRORS) {
+ trigger_error('Can not create new file in non-writable path ' . $names['dirname'], E_USER_WARNING);
+ }
+
+ return false;
+ }
+
+ return vfsStream::newFile($names['basename'])->at($dir);
+ }
+
+ /**
+ * calculates the file mode
+ *
+ * @param string $mode opening mode: r, w, a or x
+ * @param bool $extended true if + was set with opening mode
+ * @return int
+ */
+ protected function calculateMode($mode, $extended)
+ {
+ if (true === $extended) {
+ return self::ALL;
+ }
+
+ if (self::READ === $mode) {
+ return self::READONLY;
+ }
+
+ return self::WRITEONLY;
+ }
+
+ /**
+ * closes the stream
+ *
+ * @see https://github.com/mikey179/vfsStream/issues/40
+ */
+ public function stream_close()
+ {
+ $this->content->lock($this, LOCK_UN);
+ }
+
+ /**
+ * read the stream up to $count bytes
+ *
+ * @param int $count amount of bytes to read
+ * @return string
+ */
+ public function stream_read($count)
+ {
+ if (self::WRITEONLY === $this->mode) {
+ return '';
+ }
+
+ if ($this->content->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
+ return '';
+ }
+
+ return $this->content->read($count);
+ }
+
+ /**
+ * writes data into the stream
+ *
+ * @param string $data
+ * @return int amount of bytes written
+ */
+ public function stream_write($data)
+ {
+ if (self::READONLY === $this->mode) {
+ return 0;
+ }
+
+ if ($this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
+ return 0;
+ }
+
+ if (self::$quota->isLimited()) {
+ $data = substr($data, 0, self::$quota->spaceLeft(self::$root->sizeSummarized()));
+ }
+
+ return $this->content->write($data);
+ }
+
+ /**
+ * truncates a file to a given length
+ *
+ * @param int $size length to truncate file to
+ * @return bool
+ * @since 1.1.0
+ */
+ public function stream_truncate($size)
+ {
+ if (self::READONLY === $this->mode) {
+ return false;
+ }
+
+ if ($this->content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
+ return false;
+ }
+
+ if ($this->content->getType() !== vfsStreamContent::TYPE_FILE) {
+ return false;
+ }
+
+ if (self::$quota->isLimited() && $this->content->size() < $size) {
+ $maxSize = self::$quota->spaceLeft(self::$root->sizeSummarized());
+ if (0 === $maxSize) {
+ return false;
+ }
+
+ if ($size > $maxSize) {
+ $size = $maxSize;
+ }
+ }
+
+ return $this->content->truncate($size);
+ }
+
+ /**
+ * sets metadata like owner, user or permissions
+ *
+ * @param string $path
+ * @param int $option
+ * @param mixed $var
+ * @return bool
+ * @since 1.1.0
+ */
+ public function stream_metadata($path, $option, $var)
+ {
+ $path = $this->resolvePath(vfsStream::path($path));
+ $content = $this->getContent($path);
+ switch ($option) {
+ case STREAM_META_TOUCH:
+ if (null === $content) {
+ $content = $this->createFile($path, null, STREAM_REPORT_ERRORS);
+ // file creation may not be allowed at provided path
+ if (false === $content) {
+ return false;
+ }
+ }
+
+ $currentTime = time();
+ $content->lastModified(((isset($var[0])) ? ($var[0]) : ($currentTime)))
+ ->lastAccessed(((isset($var[1])) ? ($var[1]) : ($currentTime)));
+ return true;
+
+ case STREAM_META_OWNER_NAME:
+ return false;
+
+ case STREAM_META_OWNER:
+ if (null === $content) {
+ return false;
+ }
+
+ return $this->doPermChange($path,
+ $content,
+ function() use ($content, $var)
+ {
+ $content->chown($var);
+ }
+ );
+
+ case STREAM_META_GROUP_NAME:
+ return false;
+
+ case STREAM_META_GROUP:
+ if (null === $content) {
+ return false;
+ }
+
+ return $this->doPermChange($path,
+ $content,
+ function() use ($content, $var)
+ {
+ $content->chgrp($var);
+ }
+ );
+
+ case STREAM_META_ACCESS:
+ if (null === $content) {
+ return false;
+ }
+
+ return $this->doPermChange($path,
+ $content,
+ function() use ($content, $var)
+ {
+ $content->chmod($var);
+ }
+ );
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * executes given permission change when necessary rights allow such a change
+ *
+ * @param string $path
+ * @param vfsStreamAbstractContent $content
+ * @param \Closure $change
+ * @return bool
+ */
+ private function doPermChange($path, vfsStreamAbstractContent $content, \Closure $change)
+ {
+ if (!$content->isOwnedByUser(vfsStream::getCurrentUser())) {
+ return false;
+ }
+
+ if (self::$root->getName() !== $path) {
+ $names = $this->splitPath($path);
+ $parent = $this->getContent($names['dirname']);
+ if (!$parent->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) {
+ return false;
+ }
+ }
+
+ $change();
+ return true;
+ }
+
+ /**
+ * checks whether stream is at end of file
+ *
+ * @return bool
+ */
+ public function stream_eof()
+ {
+ return $this->content->eof();
+ }
+
+ /**
+ * returns the current position of the stream
+ *
+ * @return int
+ */
+ public function stream_tell()
+ {
+ return $this->content->getBytesRead();
+ }
+
+ /**
+ * seeks to the given offset
+ *
+ * @param int $offset
+ * @param int $whence
+ * @return bool
+ */
+ public function stream_seek($offset, $whence)
+ {
+ return $this->content->seek($offset, $whence);
+ }
+
+ /**
+ * flushes unstored data into storage
+ *
+ * @return bool
+ */
+ public function stream_flush()
+ {
+ return true;
+ }
+
+ /**
+ * returns status of stream
+ *
+ * @return array
+ */
+ public function stream_stat()
+ {
+ $fileStat = array('dev' => 0,
+ 'ino' => 0,
+ 'mode' => $this->content->getType() | $this->content->getPermissions(),
+ 'nlink' => 0,
+ 'uid' => $this->content->getUser(),
+ 'gid' => $this->content->getGroup(),
+ 'rdev' => 0,
+ 'size' => $this->content->size(),
+ 'atime' => $this->content->fileatime(),
+ 'mtime' => $this->content->filemtime(),
+ 'ctime' => $this->content->filectime(),
+ 'blksize' => -1,
+ 'blocks' => -1
+ );
+ return array_merge(array_values($fileStat), $fileStat);
+ }
+
+ /**
+ * retrieve the underlaying resource
+ *
+ * Please note that this method always returns false as there is no
+ * underlaying resource to return.
+ *
+ * @param int $cast_as
+ * @since 0.9.0
+ * @see https://github.com/mikey179/vfsStream/issues/3
+ * @return bool
+ */
+ public function stream_cast($cast_as)
+ {
+ return false;
+ }
+
+ /**
+ * set lock status for stream
+ *
+ * @param int $operation
+ * @return bool
+ * @since 0.10.0
+ * @see https://github.com/mikey179/vfsStream/issues/6
+ * @see https://github.com/mikey179/vfsStream/issues/31
+ * @see https://github.com/mikey179/vfsStream/issues/40
+ */
+ public function stream_lock($operation)
+ {
+ if ((LOCK_NB & $operation) == LOCK_NB) {
+ $operation = $operation - LOCK_NB;
+ }
+
+ return $this->content->lock($this, $operation);
+ }
+
+ /**
+ * sets options on the stream
+ *
+ * @param int $option key of option to set
+ * @param int $arg1
+ * @param int $arg2
+ * @return bool
+ * @since 0.10.0
+ * @see https://github.com/mikey179/vfsStream/issues/15
+ * @see http://www.php.net/manual/streamwrapper.stream-set-option.php
+ */
+ public function stream_set_option($option, $arg1, $arg2)
+ {
+ switch ($option) {
+ case STREAM_OPTION_BLOCKING:
+ // break omitted
+
+ case STREAM_OPTION_READ_TIMEOUT:
+ // break omitted
+
+ case STREAM_OPTION_WRITE_BUFFER:
+ // break omitted
+
+ default:
+ // nothing to do here
+ }
+
+ return false;
+ }
+
+ /**
+ * remove the data under the given path
+ *
+ * @param string $path
+ * @return bool
+ */
+ public function unlink($path)
+ {
+ $realPath = $this->resolvePath(vfsStream::path($path));
+ $content = $this->getContent($realPath);
+ if (null === $content) {
+ trigger_error('unlink(' . $path . '): No such file or directory', E_USER_WARNING);
+ return false;
+ }
+
+ if ($content->getType() !== vfsStreamContent::TYPE_FILE) {
+ trigger_error('unlink(' . $path . '): Operation not permitted', E_USER_WARNING);
+ return false;
+ }
+
+ return $this->doUnlink($realPath);
+ }
+
+ /**
+ * removes a path
+ *
+ * @param string $path
+ * @return bool
+ */
+ protected function doUnlink($path)
+ {
+ if (self::$root->getName() === $path) {
+ // delete root? very brave. :)
+ self::$root = null;
+ clearstatcache();
+ return true;
+ }
+
+ $names = $this->splitPath($path);
+ $content = $this->getContent($names['dirname']);
+ if (!$content->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) {
+ return false;
+ }
+
+ clearstatcache();
+ return $content->removeChild($names['basename']);
+ }
+
+ /**
+ * rename from one path to another
+ *
+ * @param string $path_from
+ * @param string $path_to
+ * @return bool
+ * @author Benoit Aubuchon
+ */
+ public function rename($path_from, $path_to)
+ {
+ $srcRealPath = $this->resolvePath(vfsStream::path($path_from));
+ $dstRealPath = $this->resolvePath(vfsStream::path($path_to));
+ $srcContent = $this->getContent($srcRealPath);
+ if (null == $srcContent) {
+ trigger_error(' No such file or directory', E_USER_WARNING);
+ return false;
+ }
+ $dstNames = $this->splitPath($dstRealPath);
+ $dstParentContent = $this->getContent($dstNames['dirname']);
+ if (null == $dstParentContent) {
+ trigger_error('No such file or directory', E_USER_WARNING);
+ return false;
+ }
+ if (!$dstParentContent->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup())) {
+ trigger_error('Permission denied', E_USER_WARNING);
+ return false;
+ }
+ if ($dstParentContent->getType() !== vfsStreamContent::TYPE_DIR) {
+ trigger_error('Target is not a directory', E_USER_WARNING);
+ return false;
+ }
+
+ // remove old source first, so we can rename later
+ // (renaming first would lead to not being able to remove the old path)
+ if (!$this->doUnlink($srcRealPath)) {
+ return false;
+ }
+
+ $dstContent = $srcContent;
+ // Renaming the filename
+ $dstContent->rename($dstNames['basename']);
+ // Copying to the destination
+ $dstParentContent->addChild($dstContent);
+ return true;
+ }
+
+ /**
+ * creates a new directory
+ *
+ * @param string $path
+ * @param int $mode
+ * @param int $options
+ * @return bool
+ */
+ public function mkdir($path, $mode, $options)
+ {
+ $umask = vfsStream::umask();
+ if (0 < $umask) {
+ $permissions = $mode & ~$umask;
+ } else {
+ $permissions = $mode;
+ }
+
+ $path = $this->resolvePath(vfsStream::path($path));
+ if (null !== $this->getContent($path)) {
+ trigger_error('mkdir(): Path vfs://' . $path . ' exists', E_USER_WARNING);
+ return false;
+ }
+
+ if (null === self::$root) {
+ self::$root = vfsStream::newDirectory($path, $permissions);
+ return true;
+ }
+
+ $maxDepth = count(explode('/', $path));
+ $names = $this->splitPath($path);
+ $newDirs = $names['basename'];
+ $dir = null;
+ $i = 0;
+ while ($dir === null && $i < $maxDepth) {
+ $dir = $this->getContent($names['dirname']);
+ $names = $this->splitPath($names['dirname']);
+ if (null == $dir) {
+ $newDirs = $names['basename'] . '/' . $newDirs;
+ }
+
+ $i++;
+ }
+
+ if (null === $dir
+ || $dir->getType() !== vfsStreamContent::TYPE_DIR
+ || $dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
+ return false;
+ }
+
+ $recursive = ((STREAM_MKDIR_RECURSIVE & $options) !== 0) ? (true) : (false);
+ if (strpos($newDirs, '/') !== false && false === $recursive) {
+ return false;
+ }
+
+ vfsStream::newDirectory($newDirs, $permissions)->at($dir);
+ return true;
+ }
+
+ /**
+ * removes a directory
+ *
+ * @param string $path
+ * @param int $options
+ * @return bool
+ * @todo consider $options with STREAM_MKDIR_RECURSIVE
+ */
+ public function rmdir($path, $options)
+ {
+ $path = $this->resolvePath(vfsStream::path($path));
+ $child = $this->getContentOfType($path, vfsStreamContent::TYPE_DIR);
+ if (null === $child) {
+ return false;
+ }
+
+ // can only remove empty directories
+ if (count($child->getChildren()) > 0) {
+ return false;
+ }
+
+ if (self::$root->getName() === $path) {
+ // delete root? very brave. :)
+ self::$root = null;
+ clearstatcache();
+ return true;
+ }
+
+ $names = $this->splitPath($path);
+ $dir = $this->getContentOfType($names['dirname'], vfsStreamContent::TYPE_DIR);
+ if ($dir->isWritable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
+ return false;
+ }
+
+ clearstatcache();
+ return $dir->removeChild($child->getName());
+ }
+
+ /**
+ * opens a directory
+ *
+ * @param string $path
+ * @param int $options
+ * @return bool
+ */
+ public function dir_opendir($path, $options)
+ {
+ $path = $this->resolvePath(vfsStream::path($path));
+ $this->dir = $this->getContentOfType($path, vfsStreamContent::TYPE_DIR);
+ if (null === $this->dir || $this->dir->isReadable(vfsStream::getCurrentUser(), vfsStream::getCurrentGroup()) === false) {
+ return false;
+ }
+
+ $this->dirIterator = $this->dir->getIterator();
+ return true;
+ }
+
+ /**
+ * reads directory contents
+ *
+ * @return string
+ */
+ public function dir_readdir()
+ {
+ $dir = $this->dirIterator->current();
+ if (null === $dir) {
+ return false;
+ }
+
+ $this->dirIterator->next();
+ return $dir->getName();
+ }
+
+ /**
+ * reset directory iteration
+ *
+ * @return bool
+ */
+ public function dir_rewinddir()
+ {
+ return $this->dirIterator->rewind();
+ }
+
+ /**
+ * closes directory
+ *
+ * @return bool
+ */
+ public function dir_closedir()
+ {
+ $this->dirIterator = null;
+ return true;
+ }
+
+ /**
+ * returns status of url
+ *
+ * @param string $path path of url to return status for
+ * @param int $flags flags set by the stream API
+ * @return array
+ */
+ public function url_stat($path, $flags)
+ {
+ $content = $this->getContent($this->resolvePath(vfsStream::path($path)));
+ if (null === $content) {
+ if (($flags & STREAM_URL_STAT_QUIET) != STREAM_URL_STAT_QUIET) {
+ trigger_error(' No such file or directory: ' . $path, E_USER_WARNING);
+ }
+
+ return false;
+
+ }
+
+ $fileStat = array('dev' => 0,
+ 'ino' => 0,
+ 'mode' => $content->getType() | $content->getPermissions(),
+ 'nlink' => 0,
+ 'uid' => $content->getUser(),
+ 'gid' => $content->getGroup(),
+ 'rdev' => 0,
+ 'size' => $content->size(),
+ 'atime' => $content->fileatime(),
+ 'mtime' => $content->filemtime(),
+ 'ctime' => $content->filectime(),
+ 'blksize' => -1,
+ 'blocks' => -1
+ );
+ return array_merge(array_values($fileStat), $fileStat);
+ }
+}