Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
24 / 24
CRAP
100.00% covered (success)
100.00%
306 / 306
Wrapper
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
24 / 24
95
100.00% covered (success)
100.00%
306 / 306
 getStatArray()
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
14 / 14
 stripScheme($path)
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 getContainerFromContext($path)
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 stream_tell()
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 stream_close()
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 stream_open($path, $mode, $options, &$openedPath)
100.00% covered (success)
100.00%
1 / 1
22
100.00% covered (success)
100.00%
3 / 3
 anonymous function () use ($path, $options)
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 stream_write($data)
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 stream_stat()
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 url_stat($path)
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
12 / 12
 stream_read($bytes)
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 stream_eof()
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 mkdir($path, $mode, $options)
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
22 / 22
 stream_metadata($path, $option, $value)
100.00% covered (success)
100.00%
1 / 1
24
100.00% covered (success)
100.00%
78 / 78
 stream_seek($offset, $whence = SEEK_SET)
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
10 / 10
 stream_truncate($newSize)
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 rename($oldname, $newname)
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
15 / 15
 unlink($path)
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
21 / 21
 rmdir($path)
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
26 / 26
 dir_opendir($path)
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
16 / 16
 dir_closedir()
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 dir_readdir()
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 dir_rewinddir()
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 stream_lock($operation)
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
<?php
/*
 * This file is part of the php-vfs package.
 *
 * (c) Michael Donat <michael.donat@me.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace VirtualFileSystem;
use VirtualFileSystem\Structure\Directory;
use VirtualFileSystem\Structure\File;
use VirtualFileSystem\Structure\Link;
use VirtualFileSystem\Structure\Root;
use VirtualFileSystem\Wrapper\DirectoryHandler;
use VirtualFileSystem\Wrapper\FileHandler;
/**
 * Stream wrapper class. This is the class that PHP uses as the stream operations handler.
 *
 * @see http://php.net/streamwrapper for informal protocol description
 *
 * @author Michael Donat <michael.donat@me.com>
 * @package php-vfs
 * @api
 */
class Wrapper
{
    public $context;
    /**
     * @var FileHandler
     */
    protected $currentlyOpenedFile;
    /**
     * @var DirectoryHandler
     */
    protected $currentlyOpenedDir;
    /**
     * Returns default expectation for stat() function.
     *
     * @see http://php.net/stat
     *
     * @return array
     */
    protected function getStatArray()
    {
        $assoc = array(
            'dev' => 0,
            'ino' => 0,
            'mode' => 0,
            'nlink' => 0,
            'uid' => 0,
            'gid' => 0,
            'rdev' => 0,
            'size' => 123,
            'atime' => 0,
            'mtime' => 0,
            'ctime' => 0,
            'blksize' => -1,
            'blocks' => -1
        );
        return array_merge(array_values($assoc), $assoc);
    }
    /**
     * Returns path stripped of url scheme (http://, ftp://, test:// etc.)
     *
     * @param string $path
     *
     * @return string
     */
    public function stripScheme($path)
    {
        $scheme = preg_split('#://#', $path, 2);
        $scheme = end($scheme);
        return '/'.ltrim($scheme, '/');
    }
    /**
     * Returns Container object fished form default_context_options by scheme.
     *
     * @param $path
     *
     * @return Container
     */
    public function getContainerFromContext($path)
    {
        $scheme = current(preg_split('#://#', $path));
        $options = stream_context_get_options(stream_context_get_default());
        return $options[$scheme]['Container'];
    }
    /**
     * @see http://php.net/streamwrapper.stream-tell
     *
     * @return int
     */
    public function stream_tell()
    {
        return $this->currentlyOpenedFile->position();
    }
    /**
     * @see http://php.net/streamwrapper.stream-close
     *
     * @return void
     */
    public function stream_close()
    {
        $this->currentlyOpenedFile = null;
    }
    /**
     * Opens stream in selected mode.
     *
     * @see http://php.net/streamwrapper.stream-open
     *
     * @param string $path
     * @param int    $mode
     * @param int    $options
     *
     * @param $openedPath
     * @throws NotDirectoryException
     * @throws NotFoundException
     * @return bool
     */
    public function stream_open($path, $mode, $options, &$openedPath)
    {
        $container = $this->getContainerFromContext($path);
        $path = $this->stripScheme($path);
        $mode = str_split(str_replace('b', '', $mode));
        $accessDeniedError = function () use ($path, $options) {
            if ($options & STREAM_REPORT_ERRORS) {
                trigger_error(sprintf('fopen(%s): failed to open stream: Permission denied', $path), E_USER_WARNING);
            }
            return false;
        };
        $appendMode = in_array('a', $mode);
        $readMode = in_array('r', $mode);
        $writeMode = in_array('w', $mode);
        $extended = in_array('+', $mode);
        if (!$container->hasNodeAt($path)) {
            if ($readMode or !$container->hasNodeAt(dirname($path))) {
                if ($options & STREAM_REPORT_ERRORS) {
                    trigger_error(sprintf('%s: failed to open stream.', $path), E_USER_WARNING);
                }
                return false;
            }
            $parent = $container->directoryAt(dirname($path));
            $permissionHelper = $container->getPermissionHelper($parent);
            if (!$permissionHelper->isWritable()) {
                return $accessDeniedError();
            }
            $parent->addFile($container->factory()->getFile(basename($path)));
        }
        $file = $container->nodeAt($path);
        if ($file instanceof Link) {
            $file = $file->getDestination();
        }
        if (($extended || $writeMode || $appendMode) && $file instanceof Directory) {
            if ($options & STREAM_REPORT_ERRORS) {
                trigger_error(sprintf('fopen(%s): failed to open stream: Is a directory', $path), E_USER_WARNING);
            }
            return false;
        }
        if ($file instanceof Directory) {
            $dir = $file;
            $file = $container->factory()->getFile('tmp');
            $file->chmod($dir->mode());
            $file->chown($dir->user());
            $file->chgrp($dir->group());
        }
        $permissionHelper = $container->getPermissionHelper($file);
        $this->currentlyOpenedFile = new FileHandler();
        $this->currentlyOpenedFile->setFile($file);
        if ($extended) {
            if (!$permissionHelper->isReadable() or !$permissionHelper->isWritable()) {
                return $accessDeniedError();
            }
            $this->currentlyOpenedFile->setReadWriteMode();
        } elseif ($readMode) {
            if (!$permissionHelper->isReadable()) {
                return $accessDeniedError();
            }
            $this->currentlyOpenedFile->setReadOnlyMode();
        } else { // a or w are for write only
            if (!$permissionHelper->isWritable()) {
                return $accessDeniedError();
            }
            $this->currentlyOpenedFile->setWriteOnlyMode();
        }
        if ($appendMode) {
            $this->currentlyOpenedFile->seekToEnd();
        } elseif ($writeMode) {
            $this->currentlyOpenedFile->truncate();
            clearstatcache();
        }
        $openedPath = $file->path();
        return true;
    }
    /**
     * Writes data to stream.
     *
     * @see http://php.net/streamwrapper.stream-write
     *
     * @param $data
     *
     * @return int
     */
    public function stream_write($data)
    {
        if (!$this->currentlyOpenedFile->isOpenedForWriting()) {
            return false;
        }
        //file access time changes so stat cache needs to be cleared
        $written = $this->currentlyOpenedFile->write($data);
        clearstatcache();
        return $written;
    }
    /**
     * Returns stat data for file inclusion. Currently disabled.
     *
     * @see http://php.net/streamwrapper.stream-stat
     *
     * @return bool
     */
    public function stream_stat()
    {
        return true;
    }
    /**
     * Returns file stat information
     *
     * @see http://php.net/stat
     *
     * @param string $path
     *
     * @return array|bool
     */
    public function url_stat($path)
    {
        try {
            $file = $this->getContainerFromContext($path)->nodeAt($this->stripScheme($path));
            return array_merge($this->getStatArray(), array(
                'mode' => $file->mode(),
                'uid' => $file->user(),
                'gid' => $file->group(),
                'atime' => $file->atime(),
                'mtime' => $file->mtime(),
                'ctime' => $file->ctime(),
                'size' => $file->size()
            ));
        } catch (NotFoundException $e) {
            return false;
        }
    }
    /**
     * Reads and returns $bytes amount of bytes from stream.
     *
     * @see http://php.net/streamwrapper.stream-read
     *
     * @param int $bytes
     *
     * @return string
     */
    public function stream_read($bytes)
    {
        if (!$this->currentlyOpenedFile->isOpenedForReading()) {
            return null;
        }
        $data = $this->currentlyOpenedFile->read($bytes);
        //file access time changes so stat cache needs to be cleared
        clearstatcache();
        return $data;
    }
    /**
     * Checks whether pointer has reached EOF.
     *
     * @see http://php.net/streamwrapper.stream-eof
     *
     * @return bool
     */
    public function stream_eof()
    {
        return $this->currentlyOpenedFile->atEof();
    }
    /**
     * Called in response to mkdir to create directory.
     *
     * @see http://php.net/streamwrapper.mkdir
     *
     * @param string $path
     * @param int    $mode
     * @param int    $options
     *
     * @return bool
     */
    public function mkdir($path, $mode, $options)
    {
        $container = $this->getContainerFromContext($path);
        $path = $this->stripScheme($path);
        $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
        try {
            //need to check all parents for permissions
            $parentPath = $path;
            while ($parentPath = dirname($parentPath)) {
                try {
                    $parent = $container->nodeAt($parentPath);
                    $permissionHelper = $container->getPermissionHelper($parent);
                    if (!$permissionHelper->isWritable()) {
                        trigger_error(sprintf('mkdir: %s: Permission denied', dirname($path)), E_USER_WARNING);
                        return false;
                    }
                    if ($parent instanceof Root) {
                        break;
                    }
                } catch (NotFoundException $e) {
                    break; //will sort missing parent below
                }
            }
            $container->createDir($path, $recursive, $mode);
        } catch (FileExistsException $e) {
            trigger_error($e->getMessage(), E_USER_WARNING);
            return false;
        } catch (NotFoundException $e) {
            trigger_error(sprintf('mkdir: %s: No such file or directory', dirname($path)), E_USER_WARNING);
            return false;
        }
        return true;
    }
    /**
     * Called in response to chown/chgrp/touch/chmod etc.
     *
     * @see http://php.net/streamwrapper.stream-metadata
     *
     * @param string  $path
     * @param int     $option
     * @param integer $value
     *
     * @return bool
     */
    public function stream_metadata($path, $option, $value)
    {
        $container = $this->getContainerFromContext($path);
        $strippedPath = $this->stripScheme($path);
        try {
            if ($option == STREAM_META_TOUCH) {
                if (!$container->hasNodeAt($strippedPath)) {
                    try {
                        $container->createFile($strippedPath);
                    } catch (NotFoundException $e) {
                        trigger_error(
                            sprintf('touch: %s: No such file or directory.', $strippedPath),
                            E_USER_WARNING
                        );
                        return false;
                    }
                }
                $file = $container->nodeAt($strippedPath);
                $permissionHelper = $container->getPermissionHelper($file);
                if (!$permissionHelper->userIsOwner() && !$permissionHelper->isWritable()) {
                    trigger_error(
                        sprintf('touch: %s: Permission denied', $strippedPath),
                        E_USER_WARNING
                    );
                    return false;
                }
                $file->setAccessTime(time());
                $file->setModificationTime(time());
                $file->setChangeTime(time());
                clearstatcache(true, $path);
                return true;
            }
            $node = $container->nodeAt($strippedPath);
            $permissionHelper = $container->getPermissionHelper($node);
            switch ($option) {
                case STREAM_META_ACCESS:
                    if ($node instanceof Link) {
                        $node = $node->getDestination();
                        $permissionHelper = $container->getPermissionHelper($node);
                    }
                    if (!$permissionHelper->userIsOwner()) {
                        trigger_error(
                            sprintf('chmod: %s: Permission denied', $strippedPath),
                            E_USER_WARNING
                        );
                        return false;
                    }
                    $node->chmod($value);
                    $node->setChangeTime(time());
                    break;
                case STREAM_META_OWNER_NAME:
                    if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) {
                        trigger_error(
                            sprintf('chown: %s: Permission denied', $strippedPath),
                            E_USER_WARNING
                        );
                        return false;
                    }
                    $uid = function_exists('posix_getpwnam') ? posix_getpwnam($value)['uid'] : 0;
                    $node->chown($uid);
                    $node->setChangeTime(time());
                    break;
                case STREAM_META_OWNER:
                    if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) {
                        trigger_error(
                            sprintf('chown: %s: Permission denied', $strippedPath),
                            E_USER_WARNING
                        );
                        return false;
                    }
                    $node->chown($value);
                    $node->setChangeTime(time());
                    break;
                case STREAM_META_GROUP_NAME:
                    if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) {
                        trigger_error(
                            sprintf('chgrp: %s: Permission denied', $strippedPath),
                            E_USER_WARNING
                        );
                        return false;
                    }
                    $gid = function_exists('posix_getgrnam') ? posix_getgrnam($value)['gid'] : 0;
                    $node->chgrp($gid);
                    $node->setChangeTime(time());
                    break;
                case STREAM_META_GROUP:
                    if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) {
                        trigger_error(
                            sprintf('chgrp: %s: Permission denied', $strippedPath),
                            E_USER_WARNING
                        );
                        return false;
                    }
                    $node->chgrp($value);
                    $node->setChangeTime(time());
                    break;
            }
        } catch (NotFoundException $e) {
            return false;
        }
        clearstatcache(true, $path);
        return true;
    }
    /**
     * Sets file pointer to specified position
     *
     * @param int $offset
     * @param int $whence
     *
     * @return bool
     */
    public function stream_seek($offset, $whence = SEEK_SET)
    {
        switch ($whence) {
            case SEEK_SET:
                $this->currentlyOpenedFile->position($offset);
                break;
            case SEEK_CUR:
                $this->currentlyOpenedFile->offsetPosition($offset);
                break;
            case SEEK_END:
                $this->currentlyOpenedFile->seekToEnd();
                $this->currentlyOpenedFile->offsetPosition($offset);
        }
        return true;
    }
    /**
     * Truncates file to given size
     *
     * @param int $newSize
     *
     * @return bool
     */
    public function stream_truncate($newSize)
    {
        $this->currentlyOpenedFile->truncate($newSize);
        clearstatcache();
        return true;
    }
    /**
     * Renames/Moves file
     *
     * @param string $oldname
     * @param string $newname
     *
     * @return bool
     */
    public function rename($oldname, $newname)
    {
        $container = $this->getContainerFromContext($newname);
        $oldname = $this->stripScheme($oldname);
        $newname = $this->stripScheme($newname);
        try {
            $container->move($oldname, $newname);
        } catch (NotFoundException $e) {
            trigger_error(
                sprintf('mv: rename %s to %s: No such file or directory', $oldname, $newname),
                E_USER_WARNING
            );
            return false;
        } catch (\RuntimeException $e) {
            trigger_error(
                sprintf('mv: rename %s to %s: Not a directory', $oldname, $newname),
                E_USER_WARNING
            );
            return false;
        }
        return true;
    }
    /**
     * Deletes file at given path
     *
     * @param int $path
     *
     * @return bool
     */
    public function unlink($path)
    {
        $container = $this->getContainerFromContext($path);
        try {
            $path = $this->stripScheme($path);
            $parent = $container->nodeAt(dirname($path));
            $permissionHelper = $container->getPermissionHelper($parent);
            if (!$permissionHelper->isWritable()) {
                trigger_error(
                    sprintf('rm: %s: Permission denied', $path),
                    E_USER_WARNING
                );
                return false;
            }
            $container->remove($path = $this->stripScheme($path));
        } catch (NotFoundException $e) {
            trigger_error(
                sprintf('rm: %s: No such file or directory', $path),
                E_USER_WARNING
            );
            return false;
        } catch (\RuntimeException $e) {
            trigger_error(
                sprintf('rm: %s: is a directory', $path),
                E_USER_WARNING
            );
            return false;
        }
        return true;
    }
    /**
     * Removes directory
     *
     * @param string $path
     *
     * @return bool
     */
    public function rmdir($path)
    {
        $container = $this->getContainerFromContext($path);
        $path = $this->stripScheme($path);
        try {
            $directory = $container->nodeAt($path);
            if ($directory instanceof File) {
                trigger_error(
                    sprintf('Warning: rmdir(%s): Not a directory', $path),
                    E_USER_WARNING
                );
                return false;
            }
            $permissionHelper = $container->getPermissionHelper($directory);
            if (!$permissionHelper->isReadable()) {
                trigger_error(
                    sprintf('rmdir: %s: Permission denied', $path),
                    E_USER_WARNING
                );
                return false;
            }
        } catch (NotFoundException $e) {
            trigger_error(
                sprintf('Warning: rmdir(%s): No such file or directory', $path),
                E_USER_WARNING
            );
            return false;
        }
        if ($directory->size()) {
            trigger_error(
                sprintf('Warning: rmdir(%s): Directory not empty', $path),
                E_USER_WARNING
            );
            return false;
        }
        $container->remove($path, true);
        return true;
    }
    /**
     * Opens directory for iteration
     *
     * @param string $path
     *
     * @return bool
     */
    public function dir_opendir($path)
    {
        $container = $this->getContainerFromContext($path);
        $path = $this->stripScheme($path);
        if (!$container->hasNodeAt($path)) {
            trigger_error(sprintf('opendir(%s): failed to open dir: No such file or directory', $path), E_USER_WARNING);
            return false;
        }
        try {
            $dir = $container->directoryAt($path);
        } catch (NotDirectoryException $e) {
            trigger_error(sprintf('opendir(%s): failed to open dir: Not a directory', $path), E_USER_WARNING);
            return false;
        }
        $permissionHelper = $container->getPermissionHelper($dir);
        if (!$permissionHelper->isReadable()) {
            trigger_error(sprintf('opendir(%s): failed to open dir: Permission denied', $path), E_USER_WARNING);
            return false;
        }
        $this->currentlyOpenedDir = new DirectoryHandler();
        $this->currentlyOpenedDir->setDirectory($dir);
        return true;
    }
    /**
     * Closes opened dir
     *
     * @return bool
     */
    public function dir_closedir()
    {
        if ($this->currentlyOpenedDir) {
            $this->currentlyOpenedDir = null;
            return true;
        }
        return false;
    }
    /**
     * Returns next file url in directory
     *
     * @return null
     */
    public function dir_readdir()
    {
        $node = $this->currentlyOpenedDir->iterator()->current();
        if (!$node) {
            return false;
        }
        $this->currentlyOpenedDir->iterator()->next();
        return $node->basename();
    }
    /**
     * Resets directory iterator
     */
    public function dir_rewinddir()
    {
        $this->currentlyOpenedDir->iterator()->rewind();
    }
    public function stream_lock($operation)
    {
        return $this->currentlyOpenedFile->lock($this, $operation);
    }
}