| <?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; |
| } |
| |
| /** |
| * sets the root content |
| * |
| * @param vfsStreamContainer $root |
| * @return vfsStreamContainer |
| */ |
| public static function setRoot(vfsStreamContainer $root) |
| { |
| self::$root = $root; |
| 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; |
| } else { |
| 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('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 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); |
| } |
| |
| if (isset($var[0])) { |
| $content->lastModified($var[0]); |
| } |
| |
| if (isset($var[1])) { |
| $content->lastAccessed($var[1]); |
| } |
| |
| 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) { |
| 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); |
| } |
| } |
| ?> |