Merge branch 'feature/streamwrappers' into develop

This commit is contained in:
Andy Miller
2014-08-19 10:53:22 -06:00
10 changed files with 773 additions and 8 deletions

View File

@@ -18,7 +18,7 @@ if (!ini_get('date.timezone')) {
$grav = Grav::instance(
[
'loader' => $loader,
'debugger' => new Debugger(Debugger::DEVELOPMENT)
'debugger' => new Debugger(Debugger::PRODUCTION)
]
);

View File

@@ -0,0 +1,36 @@
schemes:
plugin:
type: ReadOnlyStream
paths:
- user/plugins
- system/plugins
# asset:
# type: ReadOnlyStream
# paths:
# - assets
# cache:
# type: ReadOnlyStream
# paths:
# - cache
# log:
# type: ReadOnlyStream
# paths:
# - logs
page:
type: ReadOnlyStream
paths:
- user/pages
account:
type: ReadOnlyStream
paths:
- user/accounts
data:
type: ReadOnlyStream
paths:
- user/data

View File

@@ -21,7 +21,6 @@ define('LIB_DIR', SYSTEM_DIR .'src/');
define('ACCOUNTS_DIR', USER_DIR .'accounts/');
define('DATA_DIR', USER_DIR .'data/');
define('PAGES_DIR', USER_DIR .'pages/');
define('BLOCKS_DIR', USER_DIR .'blocks/');
define('PLUGINS_DIR', USER_DIR .'plugins/');
define('THEMES_DIR', USER_DIR .'themes/');

View File

@@ -1,9 +1,11 @@
<?php
namespace Grav\Common;
use Grav\Common\Service\StreamsServiceProvider;
use Grav\Component\DI\Container;
use Grav\Component\EventDispatcher\Event;
use Grav\Component\EventDispatcher\EventDispatcher;
use Grav\Component\Filesystem\ResourceLocator;
/**
* Grav
@@ -104,11 +106,16 @@ class Grav extends Container
return new \phpUserAgent();
};
$container->register(new StreamsServiceProvider);
return $container;
}
public function process()
{
// Initialize stream wrappers.
$this['locator'];
$this['plugins']->init();
$this->fireEvent('onAfterInitPlugins');

View File

@@ -41,8 +41,7 @@ class Plugins extends Iterator
continue;
}
$folder = PLUGINS_DIR . $plugin;
$filePath = $folder . DS . $plugin . PLUGIN_EXT;
$filePath = 'plugin://' . $plugin . DS . $plugin . PLUGIN_EXT;
if (!is_file($filePath)) {
throw new \RuntimeException(sprintf("Plugin '%s' enabled but not found!", $filePath, $plugin));
}
@@ -84,7 +83,7 @@ class Plugins extends Iterator
static public function all()
{
$list = array();
$iterator = new \DirectoryIterator(PLUGINS_DIR);
$iterator = new \DirectoryIterator('plugin://');
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
@@ -103,16 +102,16 @@ class Plugins extends Iterator
static public function get($type)
{
$blueprints = new Data\Blueprints(PLUGINS_DIR . $type);
$blueprints = new Data\Blueprints('plugin://' . $type);
$blueprint = $blueprints->get('blueprints');
$blueprint->name = $type;
// Load default configuration.
$file = File\Yaml::instance(PLUGINS_DIR . "{$type}/{$type}" . YAML_EXT);
$file = File\Yaml::instance('plugin://' . "{$type}/{$type}" . YAML_EXT);
$obj = new Data\Data($file->content(), $blueprint);
// Override with user configuration.
$file = File\Yaml::instance(USER_DIR . "config/plugins/{$type}" . YAML_EXT);
$file = File\Yaml::instance('plugin://' . "config/plugins/{$type}" . YAML_EXT);
$obj->merge($file->content());
// Save configuration always to user/config.

View File

@@ -0,0 +1,61 @@
<?php
namespace Grav\Common\Service;
use Grav\Component\DI\ServiceProviderInterface;
use Grav\Component\Filesystem\ResourceLocator;
use Grav\Component\Filesystem\StreamWrapper\ReadOnlyStream;
use Grav\Component\Filesystem\StreamWrapper\Stream;
use Pimple\Container;
class StreamsServiceProvider implements ServiceProviderInterface
{
public function register(Container $container)
{
$self = $this;
$container['locator'] = function($c) use ($self) {
$locator = new ResourceLocator;
$self->init($c, $locator);
return $locator;
};
}
protected function init(Container $container, ResourceLocator $locator)
{
$schemes = $container['config']->get('streams.schemes');
if (!$schemes) {
return;
}
// Set locator to both streams.
Stream::setLocator($locator);
ReadOnlyStream::setLocator($locator);
$registered = stream_get_wrappers();
foreach ($schemes as $scheme => $config) {
if (isset($config['paths'])) {
$locator->addPath($scheme, '', $config['paths']);
}
if (isset($config['prefixes'])) {
foreach ($config['prefixes'] as $prefix => $paths) {
$locator->addPath($scheme, $prefix, $paths);
}
}
if (in_array($scheme, $registered)) {
stream_wrapper_unregister($scheme);
}
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
if ($type[0] != '\\') {
$type = '\\Grav\\Component\\Filesystem\\StreamWrapper\\' . $type;
}
if (!stream_wrapper_register($scheme, $type)) {
throw new \InvalidArgumentException("Stream '{$type}' could not be initialized.");
}
}
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Grav\Component\Filesystem;
/**
* Implements Uniform Resource Location
*
* @link http://webmozarts.com/2013/06/19/the-power-of-uniform-resource-location-in-php/
*/
class ResourceLocator
{
/**
* @var array
*/
protected $schemes = [];
/**
* @param string $scheme
* @param string $prefix
* @param string|array $paths
*/
public function addPath($scheme, $prefix, $paths)
{
$list = [];
foreach((array) $paths as $path) {
$list[] = trim($path, '/');
}
if (isset($this->schemes[$scheme][$prefix])) {
$list = array_merge($list, $this->schemes[$scheme][$prefix]);
}
$this->schemes[$scheme][$prefix] = $list;
// Sort in reverse order to get longer prefixes to be matched first.
krsort($this->schemes[$scheme]);
}
/**
* @param $uri
* @return string|bool
*/
public function __invoke($uri)
{
return $this->find($uri, false);
}
/**
* @param string $uri
* @return string|bool
*/
public function findResource($uri)
{
return $this->find($uri, false);
}
/**
* @param string $uri
* @return array
*/
public function findResources($uri)
{
return $this->find($uri, true);
}
/**
* @param string $uri
* @param bool $array
* @throws \InvalidArgumentException
* @return array|string|bool
*/
protected function find($uri, $array)
{
$segments = explode('://', $uri, 2);
$file = array_pop($segments);
$scheme = array_pop($segments);
if (!$scheme) {
$scheme = 'file';
}
if (!$file || $uri[0] == ':') {
throw new \InvalidArgumentException('Invalid resource URI');
}
if (!isset($this->schemes[$scheme])) {
throw new \InvalidArgumentException("Invalid resource {$scheme}://");
}
$paths = $array ? [] : false;
foreach ($this->schemes[$scheme] as $prefix => $paths) {
if ($prefix && strpos($file, $prefix) !== 0) {
continue;
}
foreach ($paths as $path) {
$filename = ROOT_DIR . '/' . $path . '/' . ltrim(substr($file, strlen($prefix)), '\/');
if (file_exists($filename)) {
if (!$array) {
return $filename;
}
$paths[] = $filename;
}
}
}
return $paths;
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Grav\Component\Filesystem\StreamWrapper;
use Grav\Component\Filesystem\ResourceLocator;
class ReadOnlyStream extends Stream implements StreamInterface
{
/**
* @var ResourceLocator
*/
protected static $locator;
public function stream_open($uri, $mode, $options, &$opened_url)
{
if (!in_array($mode, ['r', 'rb', 'rt'])) {
if ($options & STREAM_REPORT_ERRORS) {
trigger_error('stream_open() write modes not supported for read-only stream wrappers', E_USER_WARNING);
}
return false;
}
$path = $this->getPath($uri);
if (!$path) {
return false;
}
$this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
return (bool) $this->handle;
}
public function stream_lock($operation)
{
// Disallow exclusive lock or non-blocking lock requests
if (!in_array($operation, [LOCK_SH, LOCK_UN, LOCK_SH | LOCK_NB])) {
trigger_error(
'stream_lock() exclusive lock operations not supported for read-only stream wrappers',
E_USER_WARNING
);
return false;
}
return flock($this->handle, $operation);
}
public function stream_write($data)
{
throw new \BadMethodCallException('stream_write() not supported for read-only stream wrappers');
}
public function unlink($uri)
{
throw new \BadMethodCallException('unlink() not supported for read-only stream wrappers');
}
public function rename($from_uri, $to_uri)
{
throw new \BadMethodCallException('rename() not supported for read-only stream wrappers');
}
public function mkdir($uri, $mode, $options)
{
throw new \BadMethodCallException('mkdir() not supported for read-only stream wrappers');
}
public function rmdir($uri, $options)
{
throw new \BadMethodCallException('rmdir() not supported for read-only stream wrappers');
}
}

View File

@@ -0,0 +1,232 @@
<?php
namespace Grav\Component\Filesystem\StreamWrapper;
use Grav\Component\Filesystem\ResourceLocator;
class Stream implements StreamInterface
{
/**
* A generic resource handle.
*
* @var Resource
*/
protected $handle = null;
/**
* @var ResourceLocator
*/
protected static $locator;
/**
* @param ResourceLocator $locator
*/
public static function setLocator(ResourceLocator $locator)
{
static::$locator = $locator;
}
public function stream_open($uri, $mode, $options, &$opened_url)
{
$path = $this->getPath($uri, $mode);
if (!$path) {
return false;
}
$this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
return (bool) $this->handle;
}
public function stream_close()
{
return fclose($this->handle);
}
public function stream_lock($operation)
{
if (in_array($operation, [LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB])) {
return flock($this->handle, $operation);
}
return false;
}
public function stream_metadata($uri, $option, $value)
{
switch ($option) {
case STREAM_META_TOUCH:
list ($time, $atime) = $value;
return touch($uri, $time, $atime);
case STREAM_META_OWNER_NAME:
case STREAM_META_OWNER:
return chown($uri, $value);
case STREAM_META_GROUP_NAME:
case STREAM_META_GROUP:
return chgrp($uri, $value);
case STREAM_META_ACCESS:
return chmod($uri, $value);
}
return false;
}
public function stream_read($count)
{
return fread($this->handle, $count);
}
public function stream_write($data)
{
return fwrite($this->handle, $data);
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_seek($offset, $whence)
{
// fseek returns 0 on success and -1 on a failure.
return !fseek($this->handle, $offset, $whence);
}
public function stream_flush()
{
return fflush($this->handle);
}
public function stream_tell()
{
return ftell($this->handle);
}
public function stream_stat()
{
return fstat($this->handle);
}
public function unlink($uri)
{
$path = $this->getPath($uri);
if (!$path) {
return false;
}
return unlink($path);
}
public function rename($fromUri, $toUri)
{
$fromPath = $this->getPath($fromUri);
$toPath = $this->getPath($toUri);
if (!($fromPath && $toPath)) {
return false;
}
return rename($fromPath, $toPath);
}
public function mkdir($uri, $mode, $options)
{
$recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
$path = $this->getPath($uri, $recursive ? $mode : null);
if (!$path) {
return false;
}
return ($options & STREAM_REPORT_ERRORS) ? mkdir($path, $mode, $recursive) : @mkdir($path, $mode, $recursive);
}
public function rmdir($uri, $options)
{
$path = $this->getPath($uri);
if (!$path) {
return false;
}
return ($options & STREAM_REPORT_ERRORS) ? rmdir($path) : @rmdir($path);
}
public function url_stat($uri, $flags)
{
$path = $this->getPath($uri);
if (!$path) {
return false;
}
// Suppress warnings if requested or if the file or directory does not
// exist. This is consistent with PHP's plain filesystem stream wrapper.
return ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) ? @stat($path) : stat($path);
}
public function dir_opendir($uri, $options)
{
$path = $this->getPath($uri);
if (!$path) {
return false;
}
$this->handle = opendir($path);
return (bool) $this->handle;
}
public function dir_readdir()
{
return readdir($this->handle);
}
public function dir_rewinddir()
{
rewinddir($this->handle);
return true;
}
public function dir_closedir()
{
closedir($this->handle);
return true;
}
protected function getPath($uri, $mode = null)
{
$path = $this->findPath($uri);
if ($mode == null || !$path || file_exists($path)) {
return $path;
}
if ($mode[0] == 'r') {
return false;
}
// We are either opening a file or creating directory.
list($scheme, $target) = explode('://', $uri, 2);
$path = $this->findPath($scheme . '://' . dirname($target));
if (!$path) {
return false;
}
return $path . '/' . basename($uri);
}
protected function findPath($uri)
{
return static::$locator ? static::$locator->findResource($uri) : false;
}
}

View File

@@ -0,0 +1,251 @@
<?php
namespace Grav\Component\Filesystem\StreamWrapper;
/**
* Generic PHP stream wrapper interface.
*
* @see http://www.php.net/manual/class.streamwrapper.php
*/
interface StreamInterface
{
/**
* Support for fopen(), file_get_contents(), file_put_contents() etc.
*
* @param string $uri A string containing the URI to the file to open.
* @param string $mode The file mode ("r", "wb" etc.).
* @param int $options A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
* @param string $opened_url A string containing the path actually opened.
*
* @return bool Returns TRUE if file was opened successfully.
* @see http://php.net/manual/streamwrapper.stream-open.php
*/
public function stream_open($uri, $mode, $options, &$opened_url);
/**
* Support for fclose().
*
* @return bool TRUE if stream was successfully closed.
* @see http://php.net/manual/streamwrapper.stream-close.php
*/
public function stream_close();
/**
* Support for flock().
*
* @param $operation
* One of the following:
* - LOCK_SH to acquire a shared lock (reader).
* - LOCK_EX to acquire an exclusive lock (writer).
* - LOCK_UN to release a lock (shared or exclusive).
* - LOCK_NB if you don't want flock() to block while locking (not
* supported on Windows).
*
* @return bool Always returns TRUE at the present time.
* @see http://php.net/manual/streamwrapper.stream-lock.php
*/
public function stream_lock($operation);
/**
* Support for touch(), chmod(), chown(), chgrp().
*
* @param $path
* The file path or URL to set metadata. Note that in the case of a URL, it must be a :// delimited URL.
* Other URL forms are not supported.
*
* @param $option
* One of:
* - STREAM_META_TOUCH The method was called in response to touch()
* - STREAM_META_OWNER_NAME The method was called in response to chown() with string parameter
* - STREAM_META_OWNER The method was called in response to chown()
* - STREAM_META_GROUP_NAME The method was called in response to chgrp()
* - STREAM_META_GROUP The method was called in response to chgrp()
* - STREAM_META_ACCESS The method was called in response to chmod()
*
* @param $value
* If option is
* - STREAM_META_TOUCH: Array consisting of two arguments of the touch() function.
* - STREAM_META_OWNER_NAME or
* STREAM_META_GROUP_NAME: The name of the owner user/group as string.
* - STREAM_META_OWNER or
* STREAM_META_GROUP: The value owner user/group argument as integer.
* - STREAM_META_ACCESS: The argument of the chmod() as integer.
*
* @return bool
* @see http://php.net/manual/en/streamwrapper.stream-metadata.php
*/
public function stream_metadata($path, $option, $value);
/**
* Support for fread(), file_get_contents() etc.
*
* @param $count
* Maximum number of bytes to be read.
*
* @return string|bool The string that was read, or FALSE in case of an error.
* @see http://php.net/manual/streamwrapper.stream-read.php
*/
public function stream_read($count);
/**
* Support for fwrite(), file_put_contents() etc.
*
* @param $data
* The string to be written.
*
* @return int The number of bytes written (integer).
* @see http://php.net/manual/streamwrapper.stream-write.php
*/
public function stream_write($data);
/**
* Support for feof().
*
* @return bool TRUE if end-of-file has been reached.
* @see http://php.net/manual/streamwrapper.stream-eof.php
*/
public function stream_eof();
/**
* Support for fseek().
*
* @param $offset
* The byte offset to got to.
* @param $whence
* SEEK_SET, SEEK_CUR, or SEEK_END.
*
* @return bool TRUE on success.
* @see http://php.net/manual/streamwrapper.stream-seek.php
*/
public function stream_seek($offset, $whence);
/**
* Support for fflush().
*
* @return bool TRUE if data was successfully stored (or there was no data to store).
* @see http://php.net/manual/streamwrapper.stream-flush.php
*/
public function stream_flush();
/**
* Support for ftell().
*
* @return int The current offset in bytes from the beginning of file.
* @see http://php.net/manual/streamwrapper.stream-tell.php
*/
public function stream_tell();
/**
* Support for fstat().
*
* @return array An array with file status, or FALSE in case of an error - see fstat()
* @see http://php.net/manual/streamwrapper.stream-stat.php
*/
public function stream_stat();
/**
* Support for unlink().
*
* @param $uri
* A string containing the URI to the resource to delete.
*
* @return
* TRUE if resource was successfully deleted.
* @see http://php.net/manual/streamwrapper.unlink.php
*/
public function unlink($uri);
/**
* Support for rename().
*
* @param $from_uri ,
* The URI to the file to rename.
* @param $to_uri
* The new URI for file.
*
* @return bool TRUE if file was successfully renamed.
* @see http://php.net/manual/streamwrapper.rename.php
*/
public function rename($from_uri, $to_uri);
/**
* Support for mkdir().
*
* @param $uri
* A string containing the URI to the directory to create.
* @param $mode
* Permission flags - see mkdir().
* @param $options
* A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
*
* @return bool TRUE if directory was successfully created.
* @see http://php.net/manual/streamwrapper.mkdir.php
*/
public function mkdir($uri, $mode, $options);
/**
* Support for rmdir().
*
* @param $uri
* A string containing the URI to the directory to delete.
* @param $options
* A bit mask of STREAM_REPORT_ERRORS.
*
* @return
* TRUE if directory was successfully removed.
*
* @see http://php.net/manual/streamwrapper.rmdir.php
*/
public function rmdir($uri, $options);
/**
* Support for stat().
*
* @param $uri
* A string containing the URI to get information about.
* @param $flags
* A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
*
* @return array An array with file status, or FALSE in case of an error - see fstat()
* @see http://php.net/manual/streamwrapper.url-stat.php
*/
public function url_stat($uri, $flags);
/**
* Support for opendir().
*
* @param $uri
* A string containing the URI to the directory to open.
* @param $options
* Unknown (parameter is not documented in PHP Manual).
*
* @return bool TRUE on success.
* @see http://php.net/manual/streamwrapper.dir-opendir.php
*/
public function dir_opendir($uri, $options);
/**
* Support for readdir().
*
* @return string The next filename, or FALSE if there are no more files in the directory.
* @see http://php.net/manual/streamwrapper.dir-readdir.php
*/
public function dir_readdir();
/**
* Support for rewinddir().
*
* @return bool TRUE on success.
* @see http://php.net/manual/streamwrapper.dir-rewinddir.php
*/
public function dir_rewinddir();
/**
* Support for closedir().
*
* @return bool TRUE on success.
* @see http://php.net/manual/streamwrapper.dir-closedir.php
*/
public function dir_closedir();
}