From a42144c3f77bb55df186888aad009eec6c99fcee Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 18 Aug 2014 21:24:32 +0300 Subject: [PATCH] Implement stream wrappers --- system/config/streams.yaml | 6 + system/src/Grav/Common/Grav.php | 7 + system/src/Grav/Common/Plugins.php | 11 +- .../Common/Service/StreamsServiceProvider.php | 61 +++++ .../Component/Filesystem/ResourceLocator.php | 109 ++++++++ .../StreamWrapper/ReadOnlyStream.php | 71 +++++ .../Filesystem/StreamWrapper/Stream.php | 232 ++++++++++++++++ .../StreamWrapper/StreamInterface.php | 251 ++++++++++++++++++ 8 files changed, 742 insertions(+), 6 deletions(-) create mode 100644 system/config/streams.yaml create mode 100644 system/src/Grav/Common/Service/StreamsServiceProvider.php create mode 100644 system/src/Grav/Component/Filesystem/ResourceLocator.php create mode 100644 system/src/Grav/Component/Filesystem/StreamWrapper/ReadOnlyStream.php create mode 100644 system/src/Grav/Component/Filesystem/StreamWrapper/Stream.php create mode 100644 system/src/Grav/Component/Filesystem/StreamWrapper/StreamInterface.php diff --git a/system/config/streams.yaml b/system/config/streams.yaml new file mode 100644 index 000000000..8c4f239d3 --- /dev/null +++ b/system/config/streams.yaml @@ -0,0 +1,6 @@ +schemes: + plugin: + type: ReadOnlyStream + paths: + - user/plugins + - system/plugins diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index 2f9c30fe8..1de34b00e 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -1,7 +1,9 @@ register(new StreamsServiceProvider); + return $container; } public function process() { + // Initialize stream wrappers. + $this['locator']; + $this['plugins']->init(); $this->fireEvent('onAfterInitPlugins'); diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php index 92db56461..50278e10b 100644 --- a/system/src/Grav/Common/Plugins.php +++ b/system/src/Grav/Common/Plugins.php @@ -37,8 +37,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)); } @@ -72,7 +71,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) { @@ -91,16 +90,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. diff --git a/system/src/Grav/Common/Service/StreamsServiceProvider.php b/system/src/Grav/Common/Service/StreamsServiceProvider.php new file mode 100644 index 000000000..f29ba2a30 --- /dev/null +++ b/system/src/Grav/Common/Service/StreamsServiceProvider.php @@ -0,0 +1,61 @@ +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."); + } + } + } +} diff --git a/system/src/Grav/Component/Filesystem/ResourceLocator.php b/system/src/Grav/Component/Filesystem/ResourceLocator.php new file mode 100644 index 000000000..95c202cf3 --- /dev/null +++ b/system/src/Grav/Component/Filesystem/ResourceLocator.php @@ -0,0 +1,109 @@ +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; + } +} diff --git a/system/src/Grav/Component/Filesystem/StreamWrapper/ReadOnlyStream.php b/system/src/Grav/Component/Filesystem/StreamWrapper/ReadOnlyStream.php new file mode 100644 index 000000000..dc1e88c65 --- /dev/null +++ b/system/src/Grav/Component/Filesystem/StreamWrapper/ReadOnlyStream.php @@ -0,0 +1,71 @@ +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'); + } +} diff --git a/system/src/Grav/Component/Filesystem/StreamWrapper/Stream.php b/system/src/Grav/Component/Filesystem/StreamWrapper/Stream.php new file mode 100644 index 000000000..a1307bf4f --- /dev/null +++ b/system/src/Grav/Component/Filesystem/StreamWrapper/Stream.php @@ -0,0 +1,232 @@ +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; + } +} diff --git a/system/src/Grav/Component/Filesystem/StreamWrapper/StreamInterface.php b/system/src/Grav/Component/Filesystem/StreamWrapper/StreamInterface.php new file mode 100644 index 000000000..01b9bff29 --- /dev/null +++ b/system/src/Grav/Component/Filesystem/StreamWrapper/StreamInterface.php @@ -0,0 +1,251 @@ +