mirror of
https://github.com/getgrav/grav.git
synced 2026-07-13 14:22:46 +02:00
Merge branch 'feature/1.6-cleanup' into 1.6
This commit is contained in:
26
CHANGELOG.md
26
CHANGELOG.md
@@ -4,34 +4,40 @@
|
||||
1. [](#new)
|
||||
* Added `select()` and `unselect()` methods to `CollectionInterface` and its base classes
|
||||
* Added `orderBy()` and `limit()` methods to `ObjectCollectionInterface` and its base classes
|
||||
* Flex: Added support for custom object index classes (API compatibility break)
|
||||
* Added `user-data://` which is a writable stream (`user://data` is not and should be avoided)
|
||||
* Added support for `/action:{$action}` (like task but works without nonce, used only for getting data)
|
||||
* Added support for `/action:{$action}` (like task but used without nonce when only receiving data)
|
||||
* Added `onAction.{$action}` event
|
||||
* Added `Grav\Framework\Form\FormFlash` class to contain AJAX uploaded files in more reliable way
|
||||
* Added `Grav\Framework\Form\FormFlashFile` class which implements `UploadedFileInterface` from PSR-7
|
||||
* Added `Grav\Framework\Filesystem\Filesystem` class with methods to manipulate stream URLs
|
||||
* Grav 1.6: Flex: Added support for custom object index classes (API compatibility break)
|
||||
1. [](#improved)
|
||||
* Improve Flex storage
|
||||
* Improved `Grav\Framework\File\Formatter` classes to have abstract parent class and some useful methods
|
||||
* Grav 1.6: Improved Flex storage classes
|
||||
* Grav 1.6: Improved `Grav\Framework\File` classes to use better type hints and the new `Filesystem` class
|
||||
1. [](#bugfix)
|
||||
* Fixed handling of `append_url_extension` inside of `Page::templateFormat()` [#2264](https://github.com/getgrav/grav/issues/2264)
|
||||
* Fixed a broken language string [#2261](https://github.com/getgrav/grav/issues/2261)
|
||||
* Fixed clearing cache having no effect on Doctrine cache
|
||||
* Fixed `Medium::relativePath()` for streams
|
||||
* Fixed `FlexObject::update()` call with partial object update
|
||||
* Fixed `Object` serialization breaking if overriding `jsonSerialize()` method
|
||||
* Grav 1.6: Fixed `FlexObject::update()` call with partial object update
|
||||
|
||||
# v1.6.0-beta.6
|
||||
## 11/12/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added `CsvFormatter` and `CsvFile` classes
|
||||
* Added `$grav->setup()` to simplify CLI and custom access points
|
||||
* Grav 1.6: Added `CsvFormatter` and `CsvFile` classes
|
||||
1. [](#improved)
|
||||
* Support negotiated content types set via the Request `Accept:` header
|
||||
* Support negotiated language types set via the Request `Accept-Language:` header
|
||||
* Allow custom Flex form views
|
||||
* Cleaned up and sorted the Service `idMap`
|
||||
* Grav 1.6: Allow custom Flex form views
|
||||
1. [](#bugfix)
|
||||
* Fixed `Uri::hasStandardPort()` to support reverse proxy configurations [#1786](https://github.com/getgrav/grav/issues/1786)
|
||||
* Use `append_url_extension` from page header to set template format if set [#2604](https://github.com/getgrav/grav/pull/2064)
|
||||
* Fixed some bugs in environment selection
|
||||
* Fixed some bugs in Grav environment selection logic
|
||||
|
||||
# v1.6.0-beta.5
|
||||
## 11/05/2018
|
||||
@@ -50,7 +56,7 @@
|
||||
* Set session name based on `security.salt` rather than `GRAV_ROOT` [#2242](https://github.com/getgrav/grav/issues/2242)
|
||||
* Added option to configure list of `xss_invalid_protocols` in `Security` config [#2250](https://github.com/getgrav/grav/issues/2250)
|
||||
* Smarter `security.salt` checking now we use `security.yaml` for other options
|
||||
* Merged Grav 1.5.4 fixes in
|
||||
* Grav 1.6: Merged Grav 1.5.4 fixes in
|
||||
|
||||
# v1.6.0-beta.4
|
||||
## 10/24/2018
|
||||
@@ -79,15 +85,15 @@
|
||||
## 10/09/2018
|
||||
|
||||
1. [](#new)
|
||||
* Added Flex support for custom media tasks
|
||||
* Grav 1.6: Added Flex support for custom media tasks
|
||||
1. [](#improved)
|
||||
* Added support for syslog and syslog facility logging (default: 'file')
|
||||
* Improved usability of `System` configuration blueprint with side-tabs
|
||||
1. [](#bugfix)
|
||||
* Fixed asset manager to not add empty assets when they don't exist in the filesystem
|
||||
* Regression: Fixed asset manager methods with default legacy attributes
|
||||
* Update `script` and `style` Twig tags to use the new `Assets` classes
|
||||
* Fixed asset pipeline to rewrite remote URLs as well as local [#2216](https://github.com/getgrav/grav/issues/2216)
|
||||
* Grav 1.6: Regression: Fixed asset manager methods with default legacy attributes
|
||||
|
||||
# v1.6.0-beta.1
|
||||
## 10/01/2018
|
||||
|
||||
@@ -20,7 +20,7 @@ class Container extends \Pimple\Container implements ContainerInterface
|
||||
return $this->offsetGet($id);
|
||||
}
|
||||
|
||||
public function has($id)
|
||||
public function has($id): bool
|
||||
{
|
||||
return $this->offsetExists($id);
|
||||
}
|
||||
|
||||
@@ -12,9 +12,13 @@ declare(strict_types=1);
|
||||
namespace Grav\Framework\File;
|
||||
|
||||
use Grav\Framework\File\Interfaces\FileInterface;
|
||||
use Grav\Framework\Filesystem\Filesystem;
|
||||
|
||||
class AbstractFile implements FileInterface
|
||||
{
|
||||
/** @var Filesystem */
|
||||
private $filesystem;
|
||||
|
||||
/** @var string */
|
||||
private $filepath;
|
||||
|
||||
@@ -36,8 +40,13 @@ class AbstractFile implements FileInterface
|
||||
/** @var bool */
|
||||
private $locked = false;
|
||||
|
||||
public function __construct($filepath)
|
||||
/**
|
||||
* @param string $filepath
|
||||
* @param Filesystem|null $filesystem
|
||||
*/
|
||||
public function __construct(string $filepath, Filesystem $filesystem = null)
|
||||
{
|
||||
$this->filesystem = $filesystem ?? new Filesystem();
|
||||
$this->setFilepath($filepath);
|
||||
}
|
||||
|
||||
@@ -51,11 +60,26 @@ class AbstractFile implements FileInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent cloning.
|
||||
*/
|
||||
private function __clone()
|
||||
public function __clone()
|
||||
{
|
||||
$this->handle = null;
|
||||
$this->locked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function serialize(): string
|
||||
{
|
||||
return serialize($this->doSerialize());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $serialized
|
||||
*/
|
||||
public function unserialize($serialized): void
|
||||
{
|
||||
$this->doUnserialize(unserialize($serialized, ['allowed_classes' => false]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +87,7 @@ class AbstractFile implements FileInterface
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilePath() : string
|
||||
public function getFilePath(): string
|
||||
{
|
||||
return $this->filepath;
|
||||
}
|
||||
@@ -73,7 +97,7 @@ class AbstractFile implements FileInterface
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath() : string
|
||||
public function getPath(): string
|
||||
{
|
||||
if (null === $this->path) {
|
||||
$this->setPathInfo();
|
||||
@@ -87,7 +111,7 @@ class AbstractFile implements FileInterface
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilename() : string
|
||||
public function getFilename(): string
|
||||
{
|
||||
if (null === $this->filename) {
|
||||
$this->setPathInfo();
|
||||
@@ -101,7 +125,7 @@ class AbstractFile implements FileInterface
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBasename() : string
|
||||
public function getBasename(): string
|
||||
{
|
||||
if (null === $this->basename) {
|
||||
$this->setPathInfo();
|
||||
@@ -113,10 +137,10 @@ class AbstractFile implements FileInterface
|
||||
/**
|
||||
* Return file extension.
|
||||
*
|
||||
* @param $withDot
|
||||
* @param bool $withDot
|
||||
* @return string
|
||||
*/
|
||||
public function getExtension($withDot = false) : string
|
||||
public function getExtension(bool $withDot = false): string
|
||||
{
|
||||
if (null === $this->extension) {
|
||||
$this->setPathInfo();
|
||||
@@ -130,7 +154,7 @@ class AbstractFile implements FileInterface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists() : bool
|
||||
public function exists(): bool
|
||||
{
|
||||
return is_file($this->filepath);
|
||||
}
|
||||
@@ -138,21 +162,21 @@ class AbstractFile implements FileInterface
|
||||
/**
|
||||
* Return file modification time.
|
||||
*
|
||||
* @return int|bool Timestamp or false if file doesn't exist.
|
||||
* @return int Unix timestamp. If file does not exist, method returns current time.
|
||||
*/
|
||||
public function getCreationTime()
|
||||
public function getCreationTime(): int
|
||||
{
|
||||
return is_file($this->filepath) ? filectime($this->filepath) : false;
|
||||
return is_file($this->filepath) ? filectime($this->filepath) : time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return file modification time.
|
||||
*
|
||||
* @return int|bool Timestamp or false if file doesn't exist.
|
||||
* @return int Unix timestamp. If file does not exist, method returns current time.
|
||||
*/
|
||||
public function getModificationTime()
|
||||
public function getModificationTime(): int
|
||||
{
|
||||
return is_file($this->filepath) ? filemtime($this->filepath) : false;
|
||||
return is_file($this->filepath) ? filemtime($this->filepath) : time();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +186,7 @@ class AbstractFile implements FileInterface
|
||||
* @return bool
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function lock($block = true) : bool
|
||||
public function lock(bool $block = true): bool
|
||||
{
|
||||
if (!$this->handle) {
|
||||
if (!$this->mkdir($this->getPath())) {
|
||||
@@ -184,7 +208,7 @@ class AbstractFile implements FileInterface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unlock() : bool
|
||||
public function unlock(): bool
|
||||
{
|
||||
if (!$this->handle) {
|
||||
return false;
|
||||
@@ -204,25 +228,39 @@ class AbstractFile implements FileInterface
|
||||
*
|
||||
* @return bool True = locked, false = not locked.
|
||||
*/
|
||||
public function isLocked() : bool
|
||||
public function isLocked(): bool
|
||||
{
|
||||
return $this->locked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file exists and can be read.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return is_readable($this->filepath) && is_file($this->filepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file can be written.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable() : bool
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return is_writable($this->filepath) || $this->isWritableDir($this->getPath());
|
||||
if (!file_exists($this->filepath)) {
|
||||
return $this->isWritablePath($this->getPath());
|
||||
}
|
||||
|
||||
return is_writable($this->filepath) && is_file($this->filepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)Load a file and return RAW file contents.
|
||||
* (Re)Load a file and return file contents.
|
||||
*
|
||||
* @return string
|
||||
* @return string|array|false
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
@@ -235,7 +273,7 @@ class AbstractFile implements FileInterface
|
||||
* @param mixed $data
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($data)
|
||||
public function save($data): void
|
||||
{
|
||||
$lock = false;
|
||||
if (!$this->locked) {
|
||||
@@ -266,7 +304,7 @@ class AbstractFile implements FileInterface
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function rename($path) : bool
|
||||
public function rename(string $path): bool
|
||||
{
|
||||
if ($this->exists() && !@rename($this->filepath, $path)) {
|
||||
return false;
|
||||
@@ -282,7 +320,7 @@ class AbstractFile implements FileInterface
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete() : bool
|
||||
public function delete(): bool
|
||||
{
|
||||
return @unlink($this->filepath);
|
||||
}
|
||||
@@ -293,7 +331,7 @@ class AbstractFile implements FileInterface
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function mkdir($dir) : bool
|
||||
protected function mkdir(string $dir): bool
|
||||
{
|
||||
// Silence error for open_basedir; should fail in mkdir instead.
|
||||
if (!@is_dir($dir)) {
|
||||
@@ -310,20 +348,27 @@ class AbstractFile implements FileInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
* @internal
|
||||
* @return array
|
||||
*/
|
||||
protected function isWritableDir($dir) : bool
|
||||
protected function doSerialize(): array
|
||||
{
|
||||
if ($dir && !file_exists($dir)) {
|
||||
return $this->isWritableDir(\dirname($dir));
|
||||
}
|
||||
|
||||
return $dir && is_dir($dir) && is_writable($dir);
|
||||
return [
|
||||
'filepath' => $this->filepath
|
||||
];
|
||||
}
|
||||
|
||||
protected function setFilepath($filepath) : void
|
||||
/**
|
||||
* @param array $serialized
|
||||
*/
|
||||
protected function doUnserialize(array $serialized): void
|
||||
{
|
||||
$this->setFilepath($serialized['filepath']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
*/
|
||||
protected function setFilepath(string $filepath): void
|
||||
{
|
||||
$this->filepath = $filepath;
|
||||
$this->filename = null;
|
||||
@@ -332,64 +377,32 @@ class AbstractFile implements FileInterface
|
||||
$this->extension = null;
|
||||
}
|
||||
|
||||
protected function setPathInfo() : void
|
||||
protected function setPathInfo(): void
|
||||
{
|
||||
$pathInfo = static::pathinfo($this->filepath);
|
||||
$this->filename = $pathInfo['filename'];
|
||||
$this->basename = $pathInfo['basename'];
|
||||
$this->path = $pathInfo['dirname'];
|
||||
$this->extension = $pathInfo['extension'];
|
||||
$pathInfo = $this->filesystem->pathinfo($this->filepath);
|
||||
|
||||
$this->filename = $pathInfo['filename'] ?? null;
|
||||
$this->basename = $pathInfo['basename'] ?? null;
|
||||
$this->path = $pathInfo['dirname'] ?? null;
|
||||
$this->extension = $pathInfo['extension'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-byte-safe pathinfo replacement.
|
||||
* Replacement for pathinfo(), but stream, multibyte and cross-platform safe.
|
||||
*
|
||||
* @see http://www.php.net/manual/en/function.pathinfo.php
|
||||
*
|
||||
* @param string $path A filename or path, does not need to exist as a file
|
||||
* @param int|string $options Either a PATHINFO_* constant,
|
||||
* or a string name to return only the specified piece
|
||||
*
|
||||
* @return string|array
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public static function pathinfo($path, $options = null)
|
||||
protected function isWritablePath(string $dir): bool
|
||||
{
|
||||
$ret = ['scheme' => '', 'dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
|
||||
$pathinfo = [];
|
||||
if (preg_match('#^((.*?)://)?(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$#um', $path, $pathinfo)) {
|
||||
if (array_key_exists(1, $pathinfo)) {
|
||||
$ret['scheme'] = $pathinfo[2];
|
||||
$ret['dirname'] = $pathinfo[1];
|
||||
}
|
||||
if (array_key_exists(3, $pathinfo)) {
|
||||
$ret['dirname'] .= $pathinfo[3];
|
||||
}
|
||||
if (array_key_exists(4, $pathinfo)) {
|
||||
$ret['basename'] = $pathinfo[4];
|
||||
}
|
||||
if (array_key_exists(7, $pathinfo)) {
|
||||
$ret['extension'] = $pathinfo[7];
|
||||
}
|
||||
if (array_key_exists(5, $pathinfo)) {
|
||||
$ret['filename'] = $pathinfo[5];
|
||||
}
|
||||
if ($dir === '') {
|
||||
return false;
|
||||
}
|
||||
switch ($options) {
|
||||
case PATHINFO_DIRNAME:
|
||||
case 'dirname':
|
||||
return $ret['dirname'];
|
||||
case PATHINFO_BASENAME:
|
||||
case 'basename':
|
||||
return $ret['basename'];
|
||||
case PATHINFO_EXTENSION:
|
||||
case 'extension':
|
||||
return $ret['extension'];
|
||||
case PATHINFO_FILENAME:
|
||||
case 'filename':
|
||||
return $ret['filename'];
|
||||
default:
|
||||
return $ret;
|
||||
|
||||
if (!file_exists($dir)) {
|
||||
// Recursively look up in the directory tree.
|
||||
return $this->isWritablePath($this->filesystem->parent($dir));
|
||||
}
|
||||
|
||||
return is_dir($dir) && is_writable($dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class DataFile extends AbstractFile
|
||||
/**
|
||||
* (Re)Load a file and return RAW file contents.
|
||||
*
|
||||
* @return array
|
||||
* @return array|false
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function load()
|
||||
@@ -42,7 +42,7 @@ class DataFile extends AbstractFile
|
||||
$raw = parent::load();
|
||||
|
||||
try {
|
||||
return $this->formatter->decode($raw);
|
||||
return $raw !== false ? $this->formatter->decode($raw) : false;
|
||||
} catch (RuntimeException $e) {
|
||||
throw new RuntimeException(sprintf("Failed to load file '%s': %s", $this->getFilePath(), $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
@@ -54,9 +54,10 @@ class DataFile extends AbstractFile
|
||||
* @param string|array $data Data to be saved.
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function save($data)
|
||||
public function save($data): void
|
||||
{
|
||||
if (\is_string($data)) {
|
||||
// Make sure that the string is valid data.
|
||||
try {
|
||||
$this->formatter->decode($data);
|
||||
} catch (RuntimeException $e) {
|
||||
|
||||
@@ -16,11 +16,11 @@ class File extends AbstractFile
|
||||
/**
|
||||
* Load a file from the filesystem.
|
||||
*
|
||||
* @return string
|
||||
* @return string|false
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
return (string) parent::load();
|
||||
return parent::load();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,8 +29,12 @@ class File extends AbstractFile
|
||||
* @param string $data
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($data)
|
||||
public function save($data): void
|
||||
{
|
||||
if (!\is_string($data)) {
|
||||
throw new \RuntimeException('Cannot save data, string required');
|
||||
}
|
||||
|
||||
parent::save($data);
|
||||
}
|
||||
}
|
||||
|
||||
106
system/src/Grav/Framework/File/Formatter/AbstractFormatter.php
Normal file
106
system/src/Grav/Framework/File/Formatter/AbstractFormatter.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\File\Formatter
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\File\Formatter;
|
||||
|
||||
abstract class AbstractFormatter implements FormatterInterface
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* IniFormatter constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function serialize(): string
|
||||
{
|
||||
return serialize($this->doSerialize());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $serialized
|
||||
*/
|
||||
public function unserialize($serialized): void
|
||||
{
|
||||
$this->doUnserialize(unserialize($serialized, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultFileExtension(): string
|
||||
{
|
||||
$extensions = $this->getSupportedFileExtensions();
|
||||
|
||||
// Call fails on bad configuration.
|
||||
return reset($extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSupportedFileExtensions(): array
|
||||
{
|
||||
// Call fails on bad configuration.
|
||||
return $this->getConfig('file_extension');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
abstract public function encode($data): string;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
abstract public function decode($data);
|
||||
|
||||
/**
|
||||
* Get either full configuration or a single option.
|
||||
*
|
||||
* @param string|null $name Configuration option (optional)
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getConfig(string $name = null)
|
||||
{
|
||||
if (null !== $name) {
|
||||
return $this->config[$name] ?? null;
|
||||
}
|
||||
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function doSerialize(): array
|
||||
{
|
||||
return ['config' => $this->config];
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: if overridden, make sure you call parent::doUnserialize()
|
||||
*
|
||||
* @param array $serialized
|
||||
*/
|
||||
protected function doUnserialize(array $serialized): void
|
||||
{
|
||||
$this->config = $serialized['config'];
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\File\Formatter
|
||||
*
|
||||
@@ -9,50 +11,37 @@
|
||||
|
||||
namespace Grav\Framework\File\Formatter;
|
||||
|
||||
class CsvFormatter implements FormatterInterface
|
||||
class CsvFormatter extends AbstractFormatter
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* IniFormatter constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config + [
|
||||
'file_extension' => ['.csv', '.tsv'],
|
||||
'delimiter' => ','
|
||||
];
|
||||
$config += [
|
||||
'file_extension' => ['.csv', '.tsv'],
|
||||
'delimiter' => ','
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns delimiter used to both encode and decode CSV.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDelimiter(): string
|
||||
{
|
||||
// Call fails on bad configuration.
|
||||
return $this->getConfig('delimiter');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultFileExtension()
|
||||
{
|
||||
$extensions = $this->getSupportedFileExtensions();
|
||||
|
||||
return (string) reset($extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSupportedFileExtensions()
|
||||
{
|
||||
return (array) $this->config['file_extension'];
|
||||
}
|
||||
|
||||
public function getDelimiter()
|
||||
{
|
||||
return $this->config['delimiter'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function encode($data, $delimiter = null)
|
||||
public function encode($data, $delimiter = null): string
|
||||
{
|
||||
$delimiter = $delimiter ?? $this->getDelimiter();
|
||||
$header = array_keys(reset($lines));
|
||||
@@ -71,7 +60,7 @@ class CsvFormatter implements FormatterInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($data, $delimiter = null)
|
||||
public function decode($data, $delimiter = null): array
|
||||
{
|
||||
$delimiter = $delimiter ?? $this->getDelimiter();
|
||||
$lines = preg_split('/\r\n|\r|\n/', $data);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\File\Formatter
|
||||
* @package Grav\Framework\File
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
@@ -9,7 +11,7 @@
|
||||
|
||||
namespace Grav\Framework\File\Formatter;
|
||||
|
||||
interface FormatterInterface
|
||||
interface FormatterInterface extends \Serializable
|
||||
{
|
||||
/**
|
||||
* Get default file extension from current formatter (with dot).
|
||||
@@ -18,14 +20,14 @@ interface FormatterInterface
|
||||
*
|
||||
* @return string File extension (can be empty).
|
||||
*/
|
||||
public function getDefaultFileExtension();
|
||||
public function getDefaultFileExtension(): string;
|
||||
|
||||
/**
|
||||
* Get file extensions supported by current formatter (with dot).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedFileExtensions();
|
||||
public function getSupportedFileExtensions(): array;
|
||||
|
||||
/**
|
||||
* Encode data into a string.
|
||||
@@ -33,13 +35,13 @@ interface FormatterInterface
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function encode($data);
|
||||
public function encode($data): string;
|
||||
|
||||
/**
|
||||
* Decode a string into data.
|
||||
*
|
||||
* @param string $data
|
||||
* @return array
|
||||
* @return mixed
|
||||
*/
|
||||
public function decode($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\File\Formatter
|
||||
*
|
||||
@@ -9,44 +11,25 @@
|
||||
|
||||
namespace Grav\Framework\File\Formatter;
|
||||
|
||||
class IniFormatter implements FormatterInterface
|
||||
class IniFormatter extends AbstractFormatter
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* IniFormatter constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config + [
|
||||
'file_extension' => '.ini'
|
||||
];
|
||||
$config += [
|
||||
'file_extension' => '.ini'
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultFileExtension()
|
||||
{
|
||||
$extensions = $this->getSupportedFileExtensions();
|
||||
|
||||
return (string) reset($extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSupportedFileExtensions()
|
||||
{
|
||||
return (array) $this->config['file_extension'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function encode($data)
|
||||
public function encode($data): string
|
||||
{
|
||||
$string = '';
|
||||
foreach ($data as $key => $value) {
|
||||
@@ -63,7 +46,7 @@ class IniFormatter implements FormatterInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($data)
|
||||
public function decode($data): array
|
||||
{
|
||||
$decoded = @parse_ini_string($data);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\File\Formatter
|
||||
*
|
||||
@@ -9,47 +11,70 @@
|
||||
|
||||
namespace Grav\Framework\File\Formatter;
|
||||
|
||||
class JsonFormatter implements FormatterInterface
|
||||
class JsonFormatter extends AbstractFormatter
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config + [
|
||||
$config += [
|
||||
'file_extension' => '.json',
|
||||
'encode_options' => 0,
|
||||
'decode_assoc' => true
|
||||
'decode_assoc' => true,
|
||||
'decode_depth' => 512,
|
||||
'decode_options' => 0
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options used in encode() function.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEncodeOptions(): int
|
||||
{
|
||||
return $this->getConfig('encode_options');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options used in decode() function.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDecodeOptions(): int
|
||||
{
|
||||
return $this->getConfig('decode_options');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns recursion depth used in decode() function.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDecodeDepth(): int
|
||||
{
|
||||
return $this->getConfig('decode_depth');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if JSON objects will be converted into associative arrays.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getDecodeAssoc(): bool
|
||||
{
|
||||
return $this->getConfig('decode_assoc');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultFileExtension()
|
||||
public function encode($data): string
|
||||
{
|
||||
$extensions = $this->getSupportedFileExtensions();
|
||||
$encoded = @json_encode($data, $this->getEncodeOptions());
|
||||
|
||||
return (string) reset($extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSupportedFileExtensions()
|
||||
{
|
||||
return (array) $this->config['file_extension'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function encode($data)
|
||||
{
|
||||
$encoded = @json_encode($data, $this->config['encode_options']);
|
||||
|
||||
if ($encoded === false) {
|
||||
throw new \RuntimeException('Encoding JSON failed');
|
||||
if ($encoded === false && json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \RuntimeException('Encoding JSON failed: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
return $encoded;
|
||||
@@ -60,10 +85,10 @@ class JsonFormatter implements FormatterInterface
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
$decoded = @json_decode($data, $this->config['decode_assoc']);
|
||||
$decoded = @json_decode($data, $this->getDecodeAssoc(), $this->getDecodeDepth(), $this->getDecodeOptions());
|
||||
|
||||
if ($decoded === false) {
|
||||
throw new \RuntimeException('Decoding JSON failed');
|
||||
if (null === $decoded && json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \RuntimeException('Decoding JSON failed: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\File\Formatter
|
||||
*
|
||||
@@ -9,16 +11,16 @@
|
||||
|
||||
namespace Grav\Framework\File\Formatter;
|
||||
|
||||
class MarkdownFormatter implements FormatterInterface
|
||||
use Grav\Framework\File\Interfaces\FileFormatterInterface;
|
||||
|
||||
class MarkdownFormatter extends AbstractFormatter
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
/** @var FormatterInterface */
|
||||
private $headerFormatter;
|
||||
|
||||
public function __construct(array $config = [], FormatterInterface $headerFormatter = null)
|
||||
public function __construct(array $config = [], FileFormatterInterface $headerFormatter = null)
|
||||
{
|
||||
$this->config = $config + [
|
||||
$config += [
|
||||
'file_extension' => '.md',
|
||||
'header' => 'header',
|
||||
'body' => 'markdown',
|
||||
@@ -26,34 +28,58 @@ class MarkdownFormatter implements FormatterInterface
|
||||
'yaml' => ['inline' => 20]
|
||||
];
|
||||
|
||||
$this->headerFormatter = $headerFormatter ?: new YamlFormatter($this->config['yaml']);
|
||||
parent::__construct($config);
|
||||
|
||||
$this->headerFormatter = $headerFormatter ?: new YamlFormatter($config['yaml']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns header field used in both encode() and decode().
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHeaderField(): string
|
||||
{
|
||||
return $this->getConfig('header');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns body field used in both encode() and decode().
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBodyField(): string
|
||||
{
|
||||
return $this->getConfig('body');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns raw field used in both encode() and decode().
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRawField(): string
|
||||
{
|
||||
return $this->getConfig('raw');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns header formatter object used in both encode() and decode().
|
||||
*
|
||||
* @return FileFormatterInterface
|
||||
*/
|
||||
public function getHeaderFormatter(): FileFormatterInterface
|
||||
{
|
||||
return $this->headerFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultFileExtension()
|
||||
public function encode($data): string
|
||||
{
|
||||
$extensions = $this->getSupportedFileExtensions();
|
||||
|
||||
return (string) reset($extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSupportedFileExtensions()
|
||||
{
|
||||
return (array) $this->config['file_extension'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function encode($data)
|
||||
{
|
||||
$headerVar = $this->config['header'];
|
||||
$bodyVar = $this->config['body'];
|
||||
$headerVar = $this->getHeaderField();
|
||||
$bodyVar = $this->getBodyField();
|
||||
|
||||
$header = isset($data[$headerVar]) ? (array) $data[$headerVar] : [];
|
||||
$body = isset($data[$bodyVar]) ? (string) $data[$bodyVar] : '';
|
||||
@@ -61,7 +87,7 @@ class MarkdownFormatter implements FormatterInterface
|
||||
// Create Markdown file with YAML header.
|
||||
$encoded = '';
|
||||
if ($header) {
|
||||
$encoded = "---\n" . trim($this->headerFormatter->encode($data['header'])) . "\n---\n\n";
|
||||
$encoded = "---\n" . trim($this->getHeaderFormatter()->encode($data['header'])) . "\n---\n\n";
|
||||
}
|
||||
$encoded .= $body;
|
||||
|
||||
@@ -74,12 +100,13 @@ class MarkdownFormatter implements FormatterInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($data)
|
||||
public function decode($data): array
|
||||
{
|
||||
$headerVar = $this->config['header'];
|
||||
$bodyVar = $this->config['body'];
|
||||
$rawVar = $this->config['raw'];
|
||||
$headerVar = $this->getHeaderField();
|
||||
$bodyVar = $this->getBodyField();
|
||||
$rawVar = $this->getRawField();
|
||||
|
||||
// Define empty content
|
||||
$content = [
|
||||
$headerVar => [],
|
||||
$bodyVar => ''
|
||||
@@ -100,7 +127,7 @@ class MarkdownFormatter implements FormatterInterface
|
||||
if ($rawVar) {
|
||||
$content[$rawVar] = $frontmatter;
|
||||
}
|
||||
$content[$headerVar] = $this->headerFormatter->decode($frontmatter);
|
||||
$content[$headerVar] = $this->getHeaderFormatter()->decode($frontmatter);
|
||||
$content[$bodyVar] = $matches[2];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\File\Formatter
|
||||
*
|
||||
@@ -9,44 +11,38 @@
|
||||
|
||||
namespace Grav\Framework\File\Formatter;
|
||||
|
||||
class SerializeFormatter implements FormatterInterface
|
||||
class SerializeFormatter extends AbstractFormatter
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* IniFormatter constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config + [
|
||||
'file_extension' => '.ser'
|
||||
];
|
||||
$config += [
|
||||
'file_extension' => '.ser',
|
||||
'decode_options' => ['allowed_classes' => [\stdClass::class]]
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Returns options used in decode().
|
||||
*
|
||||
* By default only allow stdClass class.
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public function getDefaultFileExtension()
|
||||
public function getOptions()
|
||||
{
|
||||
$extensions = $this->getSupportedFileExtensions();
|
||||
|
||||
return (string) reset($extensions);
|
||||
return $this->getConfig('decode_options');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSupportedFileExtensions()
|
||||
{
|
||||
return (array) $this->config['file_extension'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function encode($data)
|
||||
public function encode($data): string
|
||||
{
|
||||
return serialize($this->preserveLines($data, ["\n", "\r"], ['\\n', '\\r']));
|
||||
}
|
||||
@@ -56,9 +52,9 @@ class SerializeFormatter implements FormatterInterface
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
$decoded = @unserialize($data);
|
||||
$decoded = @unserialize($data, $this->getOptions());
|
||||
|
||||
if ($decoded === false) {
|
||||
if ($decoded === false && $data !== serialize(false)) {
|
||||
throw new \RuntimeException('Decoding serialized data failed');
|
||||
}
|
||||
|
||||
@@ -73,7 +69,7 @@ class SerializeFormatter implements FormatterInterface
|
||||
* @param array $replace
|
||||
* @return mixed
|
||||
*/
|
||||
protected function preserveLines($data, $search, $replace)
|
||||
protected function preserveLines($data, array $search, array $replace)
|
||||
{
|
||||
if (\is_string($data)) {
|
||||
$data = str_replace($search, $replace, $data);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\File\Formatter
|
||||
*
|
||||
@@ -14,50 +16,63 @@ use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
use RocketTheme\Toolbox\Compat\Yaml\Yaml as FallbackYamlParser;
|
||||
|
||||
class YamlFormatter implements FormatterInterface
|
||||
class YamlFormatter extends AbstractFormatter
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config + [
|
||||
$config += [
|
||||
'file_extension' => '.yaml',
|
||||
'inline' => 5,
|
||||
'indent' => 2,
|
||||
'native' => true,
|
||||
'compat' => true
|
||||
];
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int
|
||||
*/
|
||||
public function getDefaultFileExtension()
|
||||
public function getInlineOption(): int
|
||||
{
|
||||
$extensions = $this->getSupportedFileExtensions();
|
||||
|
||||
return (string) reset($extensions);
|
||||
return $this->getConfig('inline');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return int
|
||||
*/
|
||||
public function getSupportedFileExtensions()
|
||||
public function getIndentOption(): int
|
||||
{
|
||||
return (array) $this->config['file_extension'];
|
||||
return $this->getConfig('indent');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function useNativeDecoder(): bool
|
||||
{
|
||||
return $this->getConfig('native');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function useCompatibleDecoder(): bool
|
||||
{
|
||||
return $this->getConfig('compat');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function encode($data, $inline = null, $indent = null)
|
||||
public function encode($data, $inline = null, $indent = null): string
|
||||
{
|
||||
try {
|
||||
return (string) YamlParser::dump(
|
||||
return YamlParser::dump(
|
||||
$data,
|
||||
$inline ? (int) $inline : $this->config['inline'],
|
||||
$indent ? (int) $indent : $this->config['indent'],
|
||||
$inline ? (int) $inline : $this->getInlineOption(),
|
||||
$indent ? (int) $indent : $this->getIndentOption(),
|
||||
YamlParser::DUMP_EXCEPTION_ON_INVALID_TYPE
|
||||
);
|
||||
} catch (DumpException $e) {
|
||||
@@ -68,10 +83,10 @@ class YamlFormatter implements FormatterInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($data)
|
||||
public function decode($data): array
|
||||
{
|
||||
// Try native PECL YAML PHP extension first if available.
|
||||
if (\function_exists('yaml_parse') && $this->config['native']) {
|
||||
if (\function_exists('yaml_parse') && $this->useNativeDecoder()) {
|
||||
// Safely decode YAML.
|
||||
$saved = @ini_get('yaml.decode_php');
|
||||
@ini_set('yaml.decode_php', 0);
|
||||
@@ -79,14 +94,14 @@ class YamlFormatter implements FormatterInterface
|
||||
@ini_set('yaml.decode_php', $saved);
|
||||
|
||||
if ($decoded !== false) {
|
||||
return (array) $decoded;
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return (array) YamlParser::parse($data);
|
||||
return YamlParser::parse($data);
|
||||
} catch (ParseException $e) {
|
||||
if ($this->config['compat']) {
|
||||
if ($this->useCompatibleDecoder()) {
|
||||
return (array) FallbackYamlParser::parse($data);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,64 +11,64 @@ declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\File\Interfaces;
|
||||
|
||||
interface FileInterface
|
||||
interface FileInterface extends \Serializable
|
||||
{
|
||||
/**
|
||||
* Get full path to the file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilePath() : string;
|
||||
public function getFilePath(): string;
|
||||
|
||||
/**
|
||||
* Get path to the file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath() : string;
|
||||
public function getPath(): string;
|
||||
|
||||
/**
|
||||
* Get filename.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilename() : string;
|
||||
public function getFilename(): string;
|
||||
|
||||
/**
|
||||
* Return name of the file without extension.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBasename() : string;
|
||||
public function getBasename(): string;
|
||||
|
||||
/**
|
||||
* Return file extension.
|
||||
*
|
||||
* @param $withDot
|
||||
* @param bool $withDot
|
||||
* @return string
|
||||
*/
|
||||
public function getExtension($withDot = false) : string;
|
||||
public function getExtension(bool $withDot = false): string;
|
||||
|
||||
/**
|
||||
* Check if file exits.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists() : bool;
|
||||
public function exists(): bool;
|
||||
|
||||
/**
|
||||
* Return file modification time.
|
||||
*
|
||||
* @return int|bool Timestamp or false if file doesn't exist.
|
||||
* @return int Unix timestamp. If file does not exist, method returns current time.
|
||||
*/
|
||||
public function getCreationTime();
|
||||
public function getCreationTime(): int;
|
||||
|
||||
/**
|
||||
* Return file modification time.
|
||||
*
|
||||
* @return int|bool Timestamp or false if file doesn't exist.
|
||||
* @return int Unix timestamp. If file does not exist, method returns current time.
|
||||
*/
|
||||
public function getModificationTime();
|
||||
public function getModificationTime(): int;
|
||||
|
||||
/**
|
||||
* Lock file for writing. You need to manually unlock().
|
||||
@@ -77,33 +77,40 @@ interface FileInterface
|
||||
* @return bool
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function lock($block = true) : bool;
|
||||
public function lock(bool $block = true): bool;
|
||||
|
||||
/**
|
||||
* Unlock file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unlock() : bool;
|
||||
public function unlock(): bool;
|
||||
|
||||
/**
|
||||
* Returns true if file has been locked for writing.
|
||||
*
|
||||
* @return bool True = locked, false = not locked.
|
||||
*/
|
||||
public function isLocked() : bool;
|
||||
public function isLocked(): bool;
|
||||
|
||||
/**
|
||||
* Check if file exists and can be read.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable(): bool;
|
||||
|
||||
/**
|
||||
* Check if file can be written.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable() : bool;
|
||||
public function isWritable(): bool;
|
||||
|
||||
/**
|
||||
* (Re)Load a file and return RAW file contents.
|
||||
*
|
||||
* @return string
|
||||
* @return string|array|false
|
||||
*/
|
||||
public function load();
|
||||
|
||||
@@ -113,7 +120,7 @@ interface FileInterface
|
||||
* @param mixed $data
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($data);
|
||||
public function save($data): void;
|
||||
|
||||
/**
|
||||
* Rename file in the filesystem if it exists.
|
||||
@@ -121,26 +128,12 @@ interface FileInterface
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function rename($path) : bool;
|
||||
public function rename(string $path): bool;
|
||||
|
||||
/**
|
||||
* Delete file from filesystem.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete() : bool;
|
||||
|
||||
/**
|
||||
* Multi-byte-safe pathinfo replacement.
|
||||
* Replacement for pathinfo(), but stream, multibyte and cross-platform safe.
|
||||
*
|
||||
* @see http://www.php.net/manual/en/function.pathinfo.php
|
||||
*
|
||||
* @param string $path A filename or path, does not need to exist as a file
|
||||
* @param int|string $options Either a PATHINFO_* constant,
|
||||
* or a string name to return only the specified piece
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public static function pathinfo($path, $options = null);
|
||||
public function delete(): bool;
|
||||
}
|
||||
|
||||
268
system/src/Grav/Framework/Filesystem/Filesystem.php
Normal file
268
system/src/Grav/Framework/Filesystem/Filesystem.php
Normal file
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Filesystem
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Filesystem;
|
||||
|
||||
class Filesystem
|
||||
{
|
||||
/** @var bool|null */
|
||||
private $normalizeNext;
|
||||
|
||||
/**
|
||||
* Force paths to be normalized in the next call.
|
||||
*
|
||||
* Note: Normalizing option will be reset after the next method call.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function unsafe()
|
||||
{
|
||||
$this->normalizeNext = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell that all the paths are save to be used (already normalized) in the next call.
|
||||
*
|
||||
* Note: Normalizing option will be reset after the next method call.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function safe()
|
||||
{
|
||||
$this->normalizeNext = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parent path. Empty path is returned if there are no segments remaining.
|
||||
*
|
||||
* Can be used recursively to get towards the root directory.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $levels
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function parent(string $path, int $levels = 1): string
|
||||
{
|
||||
[$scheme, $path] = $this->getSchemeAndHierarchy($path);
|
||||
|
||||
if ($this->normalizeNext !== false) {
|
||||
$path = $this->normalizePathPart($path);
|
||||
}
|
||||
$this->normalizeNext = null;
|
||||
|
||||
if ($path === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
[$scheme, $parent] = $this->dirnameInternal($scheme, $path, $levels);
|
||||
|
||||
return $parent !== $path ? $this->toString($scheme, $parent) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize path by cleaning up \ , /./ , // and /../
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function normalize(string $path): string
|
||||
{
|
||||
if ($this->normalizeNext === false) {
|
||||
$this->normalizeNext = null;
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
[$scheme, $path] = $this->getSchemeAndHierarchy($path);
|
||||
|
||||
$path = $this->normalizePathPart($path);
|
||||
|
||||
return $this->toString($scheme, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream safe dirname() replacement.
|
||||
*
|
||||
* @see http://php.net/manual/en/function.dirname.php
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $levels
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function dirname(string $path, int $levels = 1): string
|
||||
{
|
||||
[$scheme, $path] = $this->getSchemeAndHierarchy($path);
|
||||
|
||||
if ($this->normalizeNext || ($scheme && null === $this->normalizeNext)) {
|
||||
$path = $this->normalizePathPart($path);
|
||||
}
|
||||
$this->normalizeNext = null;
|
||||
|
||||
[$scheme, $path] = $this->dirnameInternal($scheme, $path, $levels);
|
||||
|
||||
return $this->toString($scheme, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-byte and stream-safe pathinfo() replacement.
|
||||
*
|
||||
* Replacement for pathinfo(), but stream, multibyte and cross-platform safe.
|
||||
*
|
||||
* @see http://www.php.net/manual/en/function.pathinfo.php
|
||||
*
|
||||
* @param string $path A filename or path, does not need to exist as a file
|
||||
* @param int|string $options Either a PATHINFO_* constant, or a string name to return only the specified piece
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function pathinfo(string $path, int $options = null)
|
||||
{
|
||||
[$scheme, $path] = $this->getSchemeAndHierarchy($path);
|
||||
|
||||
if ($this->normalizeNext || ($scheme && null === $this->normalizeNext)) {
|
||||
$path = $this->normalizePathPart($path);
|
||||
}
|
||||
$this->normalizeNext = null;
|
||||
|
||||
return $this->pathinfoInternal($scheme, $path, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $scheme
|
||||
* @param string $path
|
||||
* @param int $levels
|
||||
* @return array
|
||||
*/
|
||||
protected function dirnameInternal(?string $scheme, string $path, int $levels = 1): array
|
||||
{
|
||||
$path = \dirname($path, $levels);
|
||||
|
||||
if (null !== $scheme && $path === '.') {
|
||||
return [$scheme, ''];
|
||||
}
|
||||
|
||||
return [$scheme, $path];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $scheme
|
||||
* @param string $path
|
||||
* @param int|null $options
|
||||
*/
|
||||
protected function pathinfoInternal(?string $scheme, string $path, int $options = null)
|
||||
{
|
||||
$info = $options ? \pathinfo($path, $options) : \pathinfo($path);
|
||||
|
||||
if (null !== $scheme) {
|
||||
$info['scheme'] = $scheme;
|
||||
$dirname = isset($info['dirname']) && $info['dirname'] !== '.' ? $info['dirname'] : null;
|
||||
|
||||
if (null !== $dirname) {
|
||||
$info['dirname'] = $scheme . '://' . $dirname;
|
||||
} else {
|
||||
$info = ['dirname' => $scheme . '://'] + $info;
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)).
|
||||
*
|
||||
* @param string $filename
|
||||
* @return array
|
||||
*/
|
||||
protected function getSchemeAndHierarchy(string $filename): array
|
||||
{
|
||||
$components = explode('://', $filename, 2);
|
||||
|
||||
return 2 === \count($components) ? $components : [null, $components[0]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $scheme
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function toString(?string $scheme, string $path): string
|
||||
{
|
||||
if ($scheme) {
|
||||
return $scheme . '://' . $path;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function normalizePathPart(string $path): string
|
||||
{
|
||||
// Quick check for empty path.
|
||||
if ($path === '' || $path === '.') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Quick check for root.
|
||||
if ($path === '/') {
|
||||
return '/';
|
||||
}
|
||||
|
||||
// If the last character is not '/' or any of '\', './', '//' and '..' are not found, path is clean and we're done.
|
||||
if ($path[-1] !== '/' && !preg_match('`(\\\\|\./|//|\.\.)`', $path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// Convert backslashes
|
||||
$path = strtr($path, ['\\' => '/']);
|
||||
|
||||
$parts = explode('/', $path);
|
||||
|
||||
// Keep absolute paths.
|
||||
$root = '';
|
||||
if ($parts[0] === '') {
|
||||
$root = '/';
|
||||
array_shift($parts);
|
||||
}
|
||||
|
||||
$list = [];
|
||||
foreach ($parts as $i => $part) {
|
||||
// Remove empty parts: // and /./
|
||||
if ($part === '' || $part === '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve /../ by removing path part.
|
||||
if ($part === '..') {
|
||||
$test = array_shift($list);
|
||||
if ($test === null) {
|
||||
// Oops, user tried to access something outside of our root folder.
|
||||
throw new \RuntimeException("Bad path {$path}");
|
||||
}
|
||||
}
|
||||
|
||||
$list[] = $part;
|
||||
}
|
||||
|
||||
// Build path back together.
|
||||
return $root . implode('/', $list);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use Grav\Common\Grav;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
|
||||
use Grav\Framework\Form\FormFlash;
|
||||
use Grav\Framework\Route\Route;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
@@ -36,31 +37,33 @@ class FlexForm implements FlexFormInterface
|
||||
private $submitted;
|
||||
/** @var string[] */
|
||||
private $errors;
|
||||
/** @var Data */
|
||||
/** @var Data|FlexObjectInterface */
|
||||
private $data;
|
||||
/** @var UploadedFileInterface[] */
|
||||
/** @var array|UploadedFileInterface[] */
|
||||
private $files;
|
||||
/** @var FlexObjectInterface */
|
||||
private $object;
|
||||
/** @var FormFlash */
|
||||
private $flash;
|
||||
|
||||
/**
|
||||
* FlexForm constructor.
|
||||
* @param string $name
|
||||
* @param FlexObjectInterface|null $object
|
||||
* @param FlexObjectInterface $object
|
||||
*/
|
||||
public function __construct(string $name = '', FlexObjectInterface $object = null)
|
||||
public function __construct(string $name, FlexObjectInterface $object)
|
||||
{
|
||||
$this->reset();
|
||||
|
||||
if ($object) {
|
||||
$this->setObject($object);
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
$this->id = $this->getName();
|
||||
$this->setObject($object);
|
||||
$this->setId($this->getName());
|
||||
$this->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML id="..." attribute.
|
||||
*
|
||||
* Defaults to 'flex-[type]-[name]', where 'type' is object type and 'name' is the first parameter given in constructor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string
|
||||
@@ -69,6 +72,8 @@ class FlexForm implements FlexFormInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets HTML id="" attribute.
|
||||
*
|
||||
* @param string $id
|
||||
*/
|
||||
public function setId(string $id): void
|
||||
@@ -77,34 +82,10 @@ class FlexForm implements FlexFormInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
$object = $this->object;
|
||||
$name = $this->name ?: 'object';
|
||||
|
||||
return "flex-{$object->getType(false)}-{$name}";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNonceName(): string
|
||||
{
|
||||
return 'nonce';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNonceAction(): string
|
||||
{
|
||||
return 'flex-object';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique id for the current form instance. By default regenerated on every page reload.
|
||||
*
|
||||
* This id is used to load the saved form state, if available.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueId(): string
|
||||
@@ -116,6 +97,51 @@ class FlexForm implements FlexFormInterface
|
||||
return $this->uniqueid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets unique form id allowing you to attach the form state to the object for example.
|
||||
*
|
||||
* @param string $uniqueId
|
||||
*/
|
||||
public function setUniqueId(string $uniqueId): void
|
||||
{
|
||||
$this->uniqueid = $uniqueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
$object = $this->getObject();
|
||||
$name = $this->name ?: 'object';
|
||||
|
||||
return "flex-{$object->getType(false)}-{$name}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFormName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNonceName(): string
|
||||
{
|
||||
return 'form-nonce';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNonceAction(): string
|
||||
{
|
||||
return 'form';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -125,29 +151,20 @@ class FlexForm implements FlexFormInterface
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getButtons(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'type' => 'submit',
|
||||
'value' => 'Save'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data|FlexObjectInterface
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
if (null === $this->data) {
|
||||
$this->data = $this->getObject();
|
||||
}
|
||||
return $this->data ?? $this->getObject();
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
/**
|
||||
* @return array|UploadedFileInterface[]
|
||||
*/
|
||||
public function getFiles(): array
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,57 +177,11 @@ class FlexForm implements FlexFormInterface
|
||||
*/
|
||||
public function getValue(string $name)
|
||||
{
|
||||
$data = $this->getData();
|
||||
return $data instanceof FlexObject ? $data->getNestedProperty($name) : $data->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UploadedFileInterface[]
|
||||
*/
|
||||
public function getFiles(): array
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileUploadAjaxRoute(): ?Route
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
if (null === $this->data) {
|
||||
return $this->getObject()->getNestedProperty($name);
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $field
|
||||
* @param $filename
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileDeleteAjaxRoute($field, $filename): ?Route
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this method clones the object.
|
||||
*
|
||||
* @param FlexObjectInterface $object
|
||||
* @return $this
|
||||
*/
|
||||
public function setObject(FlexObjectInterface $object): FlexFormInterface
|
||||
{
|
||||
$this->object = clone $object;
|
||||
|
||||
return $this;
|
||||
return $this->data->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,10 +189,6 @@ class FlexForm implements FlexFormInterface
|
||||
*/
|
||||
public function getObject(): FlexObjectInterface
|
||||
{
|
||||
if (!$this->object) {
|
||||
throw new \RuntimeException('FlexForm: Object is not defined');
|
||||
}
|
||||
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
@@ -285,26 +252,14 @@ class FlexForm implements FlexFormInterface
|
||||
}
|
||||
|
||||
$this->files = $files ?? [];
|
||||
$this->data = new Data($this->decodeData($data['data'] ?? []));
|
||||
$this->data = new Data($this->decodeData($data['data'] ?? []), $this->getBlueprint());
|
||||
if ($this->getErrors()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->validate();
|
||||
$this->doSubmit($this->data->toArray(), $this->files);
|
||||
|
||||
$this->submitted = true;
|
||||
|
||||
$object = clone $this->object;
|
||||
$object->update($this->data->toArray());
|
||||
$object->triggerEvent('onSave');
|
||||
|
||||
if (method_exists($object, 'upload')) {
|
||||
$object->upload($this->files);
|
||||
}
|
||||
$object->save();
|
||||
|
||||
$this->object = $object;
|
||||
$this->valid = true;
|
||||
} catch (ValidationException $e) {
|
||||
$list = [];
|
||||
foreach ($e->getMessages() as $field => $errors) {
|
||||
@@ -386,6 +341,33 @@ class FlexForm implements FlexFormInterface
|
||||
$this->object = $data['object'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileUploadAjaxRoute(): ?Route
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.upload');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $field
|
||||
* @param $filename
|
||||
* @return Route|null
|
||||
*/
|
||||
public function getFileDeleteAjaxRoute($field, $filename): ?Route
|
||||
{
|
||||
$object = $this->getObject();
|
||||
if (!method_exists($object, 'route')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $object->route('/edit.json/task:media.delete');
|
||||
}
|
||||
|
||||
public function getMediaTaskRoute(): string
|
||||
{
|
||||
@@ -405,6 +387,33 @@ class FlexForm implements FlexFormInterface
|
||||
return '/' . $this->object->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this method clones the object.
|
||||
*
|
||||
* @param FlexObjectInterface $object
|
||||
* @return $this
|
||||
*/
|
||||
protected function setObject(FlexObjectInterface $object): FlexFormInterface
|
||||
{
|
||||
$this->object = clone $object;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get flash object
|
||||
*
|
||||
* @return FormFlash
|
||||
*/
|
||||
protected function getFlash()
|
||||
{
|
||||
if (null === $this->flash) {
|
||||
$this->flash = new FormFlash($this->getName(), $this->getUniqueId());
|
||||
}
|
||||
|
||||
return $this->flash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
@@ -415,6 +424,41 @@ class FlexForm implements FlexFormInterface
|
||||
$this->checkUploads($this->files);
|
||||
}
|
||||
|
||||
protected function setErrors(array $errors): void
|
||||
{
|
||||
$this->errors = array_merge($this->errors, $errors);
|
||||
}
|
||||
|
||||
protected function setError(string $error): void
|
||||
{
|
||||
$this->errors[] = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $files
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function doSubmit(array $data, array $files)
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$object = clone $this->object;
|
||||
$object->update($data);
|
||||
|
||||
if (method_exists($object, 'triggerEvent')) {
|
||||
$object->triggerEvent('onSave');
|
||||
}
|
||||
|
||||
if (method_exists($object, 'upload')) {
|
||||
$object->upload($files);
|
||||
}
|
||||
|
||||
$object->save();
|
||||
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
protected function checkUploads(array $files): void
|
||||
{
|
||||
foreach ($files as $file) {
|
||||
|
||||
@@ -18,6 +18,7 @@ use Grav\Common\Page\Medium\MediumFactory;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Grav\Framework\ContentBlock\HtmlBlock;
|
||||
use Grav\Framework\Flex\Interfaces\FlexAuthorizeInterface;
|
||||
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
|
||||
use Grav\Framework\Flex\Traits\FlexAuthorizeTrait;
|
||||
use Grav\Framework\Object\Access\NestedArrayAccessTrait;
|
||||
use Grav\Framework\Object\Access\NestedPropertyTrait;
|
||||
@@ -170,7 +171,7 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
public function getForm(string $name = '')
|
||||
{
|
||||
if (!isset($this->_forms[$name])) {
|
||||
$this->_forms[$name] = new FlexForm($name, $this);
|
||||
$this->_forms[$name] = $this->createFormObject($name);
|
||||
}
|
||||
|
||||
return $this->_forms[$name];
|
||||
@@ -662,4 +663,15 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
|
||||
|
||||
unset ($elements['storage_key'], $elements['storage_timestamp']);
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods allows you to override form objects in child classes.
|
||||
*
|
||||
* @param string $name Form name
|
||||
* @return FlexFormInterface
|
||||
*/
|
||||
protected function createFormObject(string $name): FlexFormInterface
|
||||
{
|
||||
return new FlexForm($name, $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,16 @@ interface FlexFormInterface extends \Serializable
|
||||
*/
|
||||
public function setId(string $id): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueId(): string;
|
||||
|
||||
/**
|
||||
* @param string $uniqueId
|
||||
*/
|
||||
public function setUniqueId(string $uniqueId): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -47,21 +57,11 @@ interface FlexFormInterface extends \Serializable
|
||||
*/
|
||||
public function getNonceAction(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueId(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAction(): string;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getButtons() : array;
|
||||
|
||||
/**
|
||||
* @return Data|FlexObjectInterface
|
||||
*/
|
||||
@@ -94,14 +94,6 @@ interface FlexFormInterface extends \Serializable
|
||||
*/
|
||||
public function getFileDeleteAjaxRoute($field, $filename): ?Route;
|
||||
|
||||
/**
|
||||
* Note: this method clones the object.
|
||||
*
|
||||
* @param FlexObjectInterface $object
|
||||
* @return $this
|
||||
*/
|
||||
public function setObject(FlexObjectInterface $object): self;
|
||||
|
||||
/**
|
||||
* @return FlexObjectInterface
|
||||
*/
|
||||
@@ -152,20 +144,6 @@ interface FlexFormInterface extends \Serializable
|
||||
*/
|
||||
public function getBlueprint(): Blueprint;
|
||||
|
||||
/**
|
||||
* Implements \Serializable::serialize().
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize(): string;
|
||||
|
||||
/**
|
||||
* Implements \Serializable::unserialize().
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function unserialize($data): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -15,8 +15,6 @@ use Grav\Common\File\CompiledJsonFile;
|
||||
use Grav\Common\File\CompiledMarkdownFile;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Helpers\Base32;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Framework\File\Formatter\FormatterInterface;
|
||||
use Grav\Framework\File\Formatter\JsonFormatter;
|
||||
use Grav\Framework\File\Formatter\MarkdownFormatter;
|
||||
|
||||
@@ -14,7 +14,7 @@ use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Media\Traits\MediaTrait;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Plugin\Form\FormFlashFile;
|
||||
use Grav\Framework\Form\FormFlashFile;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
@@ -80,7 +80,7 @@ trait FlexMediaTrait
|
||||
}
|
||||
}
|
||||
|
||||
public function uploadMediaFile(UploadedFileInterface $uploadedFile, string $filename = null) : void
|
||||
public function uploadMediaFile(UploadedFileInterface $uploadedFile, string $filename = null, string $field = null) : void
|
||||
{
|
||||
$this->checkUploadedMediaFile($uploadedFile);
|
||||
|
||||
@@ -121,7 +121,7 @@ trait FlexMediaTrait
|
||||
$this->clearMediaCache();
|
||||
}
|
||||
|
||||
public function deleteMediaFile(string $filename) : void
|
||||
public function deleteMediaFile(string $filename, string $field = null) : void
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
$language = $grav['language'];
|
||||
|
||||
400
system/src/Grav/Framework/Form/FormFlash.php
Normal file
400
system/src/Grav/Framework/Form/FormFlash.php
Normal file
@@ -0,0 +1,400 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Form
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Form;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Session;
|
||||
use Grav\Common\User\User;
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
|
||||
class FormFlash implements \JsonSerializable
|
||||
{
|
||||
/** @var string */
|
||||
protected $form;
|
||||
/** @var string */
|
||||
protected $uniqueId;
|
||||
/** @var string */
|
||||
protected $url;
|
||||
/** @var array */
|
||||
protected $user;
|
||||
/** @var array */
|
||||
protected $uploads;
|
||||
/** @var array */
|
||||
protected $uploadObjects;
|
||||
/** @var bool */
|
||||
protected $exists;
|
||||
|
||||
/**
|
||||
* FormFlashObject constructor.
|
||||
* @param string $form
|
||||
* @param string $uniqueId
|
||||
*/
|
||||
public function __construct(string $form, $uniqueId = null)
|
||||
{
|
||||
$this->form = $form;
|
||||
$this->uniqueId = $uniqueId;
|
||||
|
||||
$file = $this->getTmpIndex();
|
||||
$this->exists = $file->exists();
|
||||
|
||||
$data = $this->exists ? (array)$file->content() : [];
|
||||
$this->url = $data['url'] ?? null;
|
||||
$this->user = $data['user'] ?? null;
|
||||
$this->uploads = $data['uploads'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFormName() : string
|
||||
{
|
||||
return $this->form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqieId() : string
|
||||
{
|
||||
return $this->uniqueId ?? $this->getFormName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exists() : bool
|
||||
{
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function save() : self
|
||||
{
|
||||
$file = $this->getTmpIndex();
|
||||
$file->save($this->jsonSerialize());
|
||||
$this->exists = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete() : self
|
||||
{
|
||||
$this->removeTmpDir();
|
||||
$this->uploads = [];
|
||||
$this->exists = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl() : string
|
||||
{
|
||||
return $this->url ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return $this
|
||||
*/
|
||||
public function setUrl(string $url) : self
|
||||
{
|
||||
$this->url = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername() : string
|
||||
{
|
||||
return $this->user['username'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail() : string
|
||||
{
|
||||
return $this->user['email'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User|null $user
|
||||
* @return $this
|
||||
*/
|
||||
public function setUser(?User $user = null) : self
|
||||
{
|
||||
if ($user && $user->username) {
|
||||
$this->user = [
|
||||
'username' => $user->username,
|
||||
'email' => $user->email
|
||||
];
|
||||
} else {
|
||||
$this->user = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @return array
|
||||
*/
|
||||
public function getFilesByField(string $field) : array
|
||||
{
|
||||
if (!isset($this->uploadObjects[$field])) {
|
||||
$objects = [];
|
||||
foreach ($this->uploads[$field] ?? [] as $filename => $upload) {
|
||||
$objects[$filename] = new FormFlashFile($field, $upload, $this);
|
||||
}
|
||||
$this->uploadObjects[$field] = $objects;
|
||||
}
|
||||
|
||||
return $this->uploadObjects[$field];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFilesByFields() : array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->uploads as $field => $values) {
|
||||
if (strpos($field, '/')) {
|
||||
continue;
|
||||
}
|
||||
$list[$field] = $this->getFilesByField($field);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @deprecated 1.6 For backwards compatibility only, do not use.
|
||||
*/
|
||||
public function getLegacyFiles() : array
|
||||
{
|
||||
$fields = [];
|
||||
foreach ($this->uploads as $field => $files) {
|
||||
if (strpos($field, '/')) {
|
||||
continue;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
$file['tmp_name'] = $this->getTmpDir() . '/' . $file['tmp_name'];
|
||||
$fields[$field][$file['path'] ?? $file['name']] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $filename
|
||||
* @param array $upload
|
||||
* @return bool
|
||||
*/
|
||||
public function uploadFile(string $field, string $filename, array $upload) : bool
|
||||
{
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
|
||||
Folder::create($tmp_dir);
|
||||
|
||||
$tmp_file = $upload['file']['tmp_name'];
|
||||
$basename = basename($tmp_file);
|
||||
|
||||
if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$upload['file']['tmp_name'] = $basename;
|
||||
|
||||
if (!isset($this->uploads[$field])) {
|
||||
$this->uploads[$field] = [];
|
||||
}
|
||||
|
||||
// Prepare object for later save
|
||||
$upload['file']['name'] = $filename;
|
||||
|
||||
// Replace old file, including original
|
||||
$oldUpload = $this->uploads[$field][$filename] ?? null;
|
||||
if (isset($oldUpload['tmp_name'])) {
|
||||
$this->removeTmpFile($oldUpload['tmp_name']);
|
||||
}
|
||||
|
||||
$originalUpload = $this->uploads[$field . '/original'][$filename] ?? null;
|
||||
if (isset($originalUpload['tmp_name'])) {
|
||||
$this->removeTmpFile($originalUpload['tmp_name']);
|
||||
unset($this->uploads[$field . '/original'][$filename]);
|
||||
}
|
||||
|
||||
// Prepare data to be saved later
|
||||
$this->uploads[$field][$filename] = $upload['file'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $filename
|
||||
* @param array $upload
|
||||
* @param array $crop
|
||||
* @return bool
|
||||
*/
|
||||
public function cropFile(string $field, string $filename, array $upload, array $crop) : bool
|
||||
{
|
||||
$tmp_dir = $this->getTmpDir();
|
||||
|
||||
Folder::create($tmp_dir);
|
||||
|
||||
$tmp_file = $upload['file']['tmp_name'];
|
||||
$basename = basename($tmp_file);
|
||||
|
||||
if (!move_uploaded_file($tmp_file, $tmp_dir . '/' . $basename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$upload['file']['tmp_name'] = $basename;
|
||||
|
||||
if (!isset($this->uploads[$field])) {
|
||||
$this->uploads[$field] = [];
|
||||
}
|
||||
|
||||
// Prepare object for later save
|
||||
$upload['file']['name'] = $filename;
|
||||
|
||||
$oldUpload = $this->uploads[$field][$filename] ?? null;
|
||||
if ($oldUpload) {
|
||||
$originalUpload = $this->uploads[$field . '/original'][$filename] ?? null;
|
||||
if ($originalUpload) {
|
||||
$this->removeTmpFile($oldUpload['tmp_name']);
|
||||
} else {
|
||||
$oldUpload['crop'] = $crop;
|
||||
$this->uploads[$field . '/original'][$filename] = $oldUpload;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare data to be saved later
|
||||
$this->uploads[$field][$filename] = $upload['file'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
public function removeFile(string $field, string $filename) : bool
|
||||
{
|
||||
if (!$field || !$filename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = $this->getTmpIndex();
|
||||
if (!$file->exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$upload = $this->uploads[$field][$filename] ?? null;
|
||||
if (null !== $upload) {
|
||||
$this->removeTmpFile($upload['tmp_name'] ?? '');
|
||||
}
|
||||
$upload = $this->uploads[$field . '/original'][$filename] ?? null;
|
||||
if (null !== $upload) {
|
||||
$this->removeTmpFile($upload['tmp_name'] ?? '');
|
||||
}
|
||||
|
||||
// Walk backward to cleanup any empty field that's left
|
||||
unset(
|
||||
$this->uploadObjects[$field][$filename],
|
||||
$this->uploads[$field][$filename],
|
||||
$this->uploadObjects[$field . '/original'][$filename],
|
||||
$this->uploads[$field . '/original'][$filename]
|
||||
);
|
||||
if (empty($this->uploads[$field])) {
|
||||
unset($this->uploads[$field]);
|
||||
}
|
||||
if (empty($this->uploads[$field . '/original'])) {
|
||||
unset($this->uploads[$field . '/original']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize() : array
|
||||
{
|
||||
return [
|
||||
'form' => $this->form,
|
||||
'unique_id' => $this->uniqueId,
|
||||
'url' => $this->url,
|
||||
'user' => $this->user,
|
||||
'uploads' => $this->uploads
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTmpDir() : string
|
||||
{
|
||||
$grav = Grav::instance();
|
||||
|
||||
/** @var Session $session */
|
||||
$session = $grav['session'];
|
||||
|
||||
$location = [
|
||||
'forms',
|
||||
$session->getId(),
|
||||
$this->uniqueId ?: $this->form
|
||||
];
|
||||
|
||||
return $grav['locator']->findResource('tmp://', true, true) . '/' . implode('/', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return YamlFile
|
||||
*/
|
||||
protected function getTmpIndex() : YamlFile
|
||||
{
|
||||
// Do not use CompiledYamlFile as the file can change multiple times per second.
|
||||
return YamlFile::instance($this->getTmpDir() . '/index.yaml');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
protected function removeTmpFile(string $name) : void
|
||||
{
|
||||
$filename = $this->getTmpDir() . '/' . $name;
|
||||
if ($name && is_file($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
|
||||
protected function removeTmpDir() : void
|
||||
{
|
||||
$tmpDir = $this->getTmpDir();
|
||||
if (file_exists($tmpDir)) {
|
||||
Folder::delete($tmpDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
system/src/Grav/Framework/Form/FormFlashFile.php
Normal file
146
system/src/Grav/Framework/Form/FormFlashFile.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Framework\Form
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Form;
|
||||
|
||||
use Grav\Framework\Psr7\Stream;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
|
||||
class FormFlashFile implements UploadedFileInterface, \JsonSerializable
|
||||
{
|
||||
private $field;
|
||||
private $moved = false;
|
||||
private $upload;
|
||||
private $flash;
|
||||
|
||||
public function __construct(string $field, array $upload, FormFlash $flash)
|
||||
{
|
||||
$this->field = $field;
|
||||
$this->upload = $upload;
|
||||
$this->flash = $flash;
|
||||
|
||||
if ($this->isOk() && (empty($this->upload['tmp_name']) || !file_exists($this->getTmpFile()))) {
|
||||
$this->upload['error'] = \UPLOAD_ERR_NO_FILE;
|
||||
}
|
||||
|
||||
if (!isset($this->upload['size'])) {
|
||||
$this->upload['size'] = $this->isOk() ? filesize($this->getTmpFile()) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StreamInterface
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
$this->validateActive();
|
||||
|
||||
$resource = \fopen($this->getTmpFile(), 'rb');
|
||||
|
||||
return Stream::create($resource);
|
||||
}
|
||||
|
||||
public function moveTo($targetPath)
|
||||
{
|
||||
$this->validateActive();
|
||||
|
||||
if (!\is_string($targetPath) || empty($targetPath)) {
|
||||
throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
|
||||
}
|
||||
|
||||
$this->moved = \copy($this->getTmpFile(), $targetPath);
|
||||
|
||||
if (false === $this->moved) {
|
||||
throw new \RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath));
|
||||
}
|
||||
|
||||
$this->flash->removeFile($this->field, $this->upload['tmp_name']);
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return $this->upload['size'];
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->upload['error'] ?? \UPLOAD_ERR_OK;
|
||||
}
|
||||
|
||||
public function getClientFilename()
|
||||
{
|
||||
return $this->upload['name'] ?? 'unknown';
|
||||
}
|
||||
|
||||
public function getClientMediaType()
|
||||
{
|
||||
return $this->upload['type'] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
public function isMoved() : bool
|
||||
{
|
||||
return $this->moved;
|
||||
}
|
||||
|
||||
public function getMetaData() : array
|
||||
{
|
||||
if (isset($this->upload['crop'])) {
|
||||
return ['crop' => $this->upload['crop']];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getDestination()
|
||||
{
|
||||
return $this->upload['path'];
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->upload;
|
||||
}
|
||||
|
||||
public function __debugInfo()
|
||||
{
|
||||
return [
|
||||
'field:private' => $this->field,
|
||||
'moved:private' => $this->moved,
|
||||
'upload:private' => $this->upload,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RuntimeException if is moved or not ok
|
||||
*/
|
||||
private function validateActive(): void
|
||||
{
|
||||
if (!$this->isOk()) {
|
||||
throw new \RuntimeException('Cannot retrieve stream due to upload error');
|
||||
}
|
||||
|
||||
if ($this->moved) {
|
||||
throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool return true if there is no upload error
|
||||
*/
|
||||
private function isOk(): bool
|
||||
{
|
||||
return \UPLOAD_ERR_OK === $this->getError();
|
||||
}
|
||||
|
||||
private function getTmpFile() : string
|
||||
{
|
||||
return $this->flash->getTmpDir() . '/' . $this->upload['tmp_name'];
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ trait ObjectTrait
|
||||
*/
|
||||
protected function doSerialize()
|
||||
{
|
||||
return $this->jsonSerialize();
|
||||
return ['key' => $this->getKey(), 'type' => $this->getType(), 'elements' => $this->getElements()];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,7 +163,7 @@ trait ObjectTrait
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return ['key' => $this->getKey(), 'type' => $this->getType(), 'elements' => $this->getElements()];
|
||||
return $this->doSerialize();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
class_alias('Nyholm\Psr7\Request', 'Grav\Framework\Psr7\Request');
|
||||
class_alias(Nyholm\Psr7\Request::class, Grav\Framework\Psr7\Request::class);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
class_alias('Nyholm\Psr7\Response', 'Grav\Framework\Psr7\Response');
|
||||
class_alias(Nyholm\Psr7\Response::class, Grav\Framework\Psr7\Response::class);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
class_alias('Nyholm\Psr7\ServerRequest', 'Grav\Framework\Psr7\ServerRequest');
|
||||
class_alias(Nyholm\Psr7\ServerRequest::class, Grav\Framework\Psr7\ServerRequest::class);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
class_alias('Nyholm\Psr7\Stream', 'Grav\Framework\Psr7\Stream');
|
||||
class_alias(Nyholm\Psr7\Stream::class, Grav\Framework\Psr7\Stream::class);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
class_alias('Nyholm\Psr7\UploadedFile', 'Grav\Framework\Psr7\UploadedFile');
|
||||
class_alias(Nyholm\Psr7\UploadedFile::class, Grav\Framework\Psr7\UploadedFile::class);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
class_alias('Nyholm\Psr7\Uri', 'Grav\Framework\Psr7\Uri');
|
||||
class_alias(Nyholm\Psr7\Uri::class, Grav\Framework\Psr7\Uri::class);
|
||||
|
||||
@@ -11,17 +11,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Grav\Framework\RequestHandler\Exception;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class NotHandledException extends NotFoundException
|
||||
{
|
||||
/**
|
||||
* NotFoundException constructor.
|
||||
* @param ServerRequestInterface $request
|
||||
* @param \Throwable|null $previous
|
||||
*/
|
||||
public function __construct(ServerRequestInterface $request, \Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($request, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ interface SessionInterface extends \IteratorAggregate
|
||||
/**
|
||||
* Checks if the session was started.
|
||||
*
|
||||
* @return Boolean
|
||||
* @return bool
|
||||
*/
|
||||
public function isStarted();
|
||||
|
||||
|
||||
322
tests/unit/Grav/Framework/Filesystem/FilesystemTest.php
Normal file
322
tests/unit/Grav/Framework/Filesystem/FilesystemTest.php
Normal file
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
use Grav\Framework\Filesystem\Filesystem;
|
||||
|
||||
class FilesystemTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
protected $class;
|
||||
|
||||
protected $tests = [
|
||||
'' => [
|
||||
'parent' => '',
|
||||
'normalize' => '',
|
||||
'dirname' => '',
|
||||
'pathinfo' => [
|
||||
'basename' => '',
|
||||
'filename' => '',
|
||||
]
|
||||
],
|
||||
'.' => [
|
||||
'parent' => '',
|
||||
'normalize' => '',
|
||||
'dirname' => '.',
|
||||
'pathinfo' => [
|
||||
'dirname' => '.',
|
||||
'basename' => '.',
|
||||
'extension' => '',
|
||||
'filename' => '',
|
||||
]
|
||||
],
|
||||
'./' => [
|
||||
'parent' => '',
|
||||
'normalize' => '',
|
||||
'dirname' => '.',
|
||||
'pathinfo' => [
|
||||
'dirname' => '.',
|
||||
'basename' => '.',
|
||||
'extension' => '',
|
||||
'filename' => '',
|
||||
|
||||
]
|
||||
],
|
||||
'././.' => [
|
||||
'parent' => '',
|
||||
'normalize' => '',
|
||||
'dirname' => './.',
|
||||
'pathinfo' => [
|
||||
'dirname' => './.',
|
||||
'basename' => '.',
|
||||
'extension' => '',
|
||||
'filename' => '',
|
||||
]
|
||||
],
|
||||
'.file' => [
|
||||
'parent' => '.',
|
||||
'normalize' => '.file',
|
||||
'dirname' => '.',
|
||||
'pathinfo' => [
|
||||
'dirname' => '.',
|
||||
'basename' => '.file',
|
||||
'extension' => 'file',
|
||||
'filename' => '',
|
||||
]
|
||||
],
|
||||
'/' => [
|
||||
'parent' => '',
|
||||
'normalize' => '/',
|
||||
'dirname' => '/',
|
||||
'pathinfo' => [
|
||||
'dirname' => '/',
|
||||
'basename' => '',
|
||||
'filename' => '',
|
||||
]
|
||||
],
|
||||
'/absolute' => [
|
||||
'parent' => '/',
|
||||
'normalize' => '/absolute',
|
||||
'dirname' => '/',
|
||||
'pathinfo' => [
|
||||
'dirname' => '/',
|
||||
'basename' => 'absolute',
|
||||
'filename' => 'absolute',
|
||||
]
|
||||
],
|
||||
'/absolute/' => [
|
||||
'parent' => '/',
|
||||
'normalize' => '/absolute',
|
||||
'dirname' => '/',
|
||||
'pathinfo' => [
|
||||
'dirname' => '/',
|
||||
'basename' => 'absolute',
|
||||
'filename' => 'absolute',
|
||||
]
|
||||
],
|
||||
'/very/long/absolute/path' => [
|
||||
'parent' => '/very/long/absolute',
|
||||
'normalize' => '/very/long/absolute/path',
|
||||
'dirname' => '/very/long/absolute',
|
||||
'pathinfo' => [
|
||||
'dirname' => '/very/long/absolute',
|
||||
'basename' => 'path',
|
||||
'filename' => 'path',
|
||||
]
|
||||
],
|
||||
'relative' => [
|
||||
'parent' => '.',
|
||||
'normalize' => 'relative',
|
||||
'dirname' => '.',
|
||||
'pathinfo' => [
|
||||
'dirname' => '.',
|
||||
'basename' => 'relative',
|
||||
'filename' => 'relative',
|
||||
]
|
||||
],
|
||||
'very/long/relative/path' => [
|
||||
'parent' => 'very/long/relative',
|
||||
'normalize' => 'very/long/relative/path',
|
||||
'dirname' => 'very/long/relative',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'very/long/relative',
|
||||
'basename' => 'path',
|
||||
'filename' => 'path',
|
||||
]
|
||||
],
|
||||
'path/to/file.jpg' => [
|
||||
'parent' => 'path/to',
|
||||
'normalize' => 'path/to/file.jpg',
|
||||
'dirname' => 'path/to',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'path/to',
|
||||
'basename' => 'file.jpg',
|
||||
'extension' => 'jpg',
|
||||
'filename' => 'file',
|
||||
]
|
||||
],
|
||||
'user://' => [
|
||||
'parent' => '',
|
||||
'normalize' => 'user://',
|
||||
'dirname' => 'user://',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user://',
|
||||
'basename' => '',
|
||||
'filename' => '',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user://.' => [
|
||||
'parent' => '',
|
||||
'normalize' => 'user://',
|
||||
'dirname' => 'user://',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user://',
|
||||
'basename' => '',
|
||||
'filename' => '',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user://././.' => [
|
||||
'parent' => '',
|
||||
'normalize' => 'user://',
|
||||
'dirname' => 'user://',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user://',
|
||||
'basename' => '',
|
||||
'filename' => '',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user://./././file' => [
|
||||
'parent' => 'user://',
|
||||
'normalize' => 'user://file',
|
||||
'dirname' => 'user://',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user://',
|
||||
'basename' => 'file',
|
||||
'filename' => 'file',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user://./././folder/file' => [
|
||||
'parent' => 'user://folder',
|
||||
'normalize' => 'user://folder/file',
|
||||
'dirname' => 'user://folder',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user://folder',
|
||||
'basename' => 'file',
|
||||
'filename' => 'file',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user://.file' => [
|
||||
'parent' => 'user://',
|
||||
'normalize' => 'user://.file',
|
||||
'dirname' => 'user://',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user://',
|
||||
'basename' => '.file',
|
||||
'extension' => 'file',
|
||||
'filename' => '',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user:///' => [
|
||||
'parent' => '',
|
||||
'normalize' => 'user:///',
|
||||
'dirname' => 'user:///',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user:///',
|
||||
'basename' => '',
|
||||
'filename' => '',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user:///absolute' => [
|
||||
'parent' => 'user:///',
|
||||
'normalize' => 'user:///absolute',
|
||||
'dirname' => 'user:///',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user:///',
|
||||
'basename' => 'absolute',
|
||||
'filename' => 'absolute',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user:///very/long/absolute/path' => [
|
||||
'parent' => 'user:///very/long/absolute',
|
||||
'normalize' => 'user:///very/long/absolute/path',
|
||||
'dirname' => 'user:///very/long/absolute',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user:///very/long/absolute',
|
||||
'basename' => 'path',
|
||||
'filename' => 'path',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user://relative' => [
|
||||
'parent' => 'user://',
|
||||
'normalize' => 'user://relative',
|
||||
'dirname' => 'user://',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user://',
|
||||
'basename' => 'relative',
|
||||
'filename' => 'relative',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user://very/long/relative/path' => [
|
||||
'parent' => 'user://very/long/relative',
|
||||
'normalize' => 'user://very/long/relative/path',
|
||||
'dirname' => 'user://very/long/relative',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user://very/long/relative',
|
||||
'basename' => 'path',
|
||||
'filename' => 'path',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
'user://path/to/file.jpg' => [
|
||||
'parent' => 'user://path/to',
|
||||
'normalize' => 'user://path/to/file.jpg',
|
||||
'dirname' => 'user://path/to',
|
||||
'pathinfo' => [
|
||||
'dirname' => 'user://path/to',
|
||||
'basename' => 'file.jpg',
|
||||
'extension' => 'jpg',
|
||||
'filename' => 'file',
|
||||
'scheme' => 'user',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
protected function _before()
|
||||
{
|
||||
$this->class = new Filesystem();
|
||||
}
|
||||
|
||||
protected function _after()
|
||||
{
|
||||
unset($this->class);
|
||||
}
|
||||
|
||||
protected function runTestSet(array $tests, $method)
|
||||
{
|
||||
$class = $this->class;
|
||||
foreach ($tests as $path => $candidates) {
|
||||
if (!array_key_exists($method, $candidates)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expected = $candidates[$method];
|
||||
|
||||
$result = $class->{$method}($path);
|
||||
|
||||
$this->assertSame($expected, $result, "Test {$method}('{$path}')");
|
||||
|
||||
if (function_exists($method) && !strpos($path, '://')) {
|
||||
$cmp_result = $method($path);
|
||||
|
||||
$this->assertSame($cmp_result, $result, "Compare to original {$method}('{$path}')");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testParent()
|
||||
{
|
||||
$this->runTestSet($this->tests, 'parent');
|
||||
}
|
||||
|
||||
public function testNormalize()
|
||||
{
|
||||
$this->runTestSet($this->tests, 'normalize');
|
||||
}
|
||||
|
||||
public function testDirname()
|
||||
{
|
||||
$this->runTestSet($this->tests, 'dirname');
|
||||
}
|
||||
|
||||
public function testPathinfo()
|
||||
{
|
||||
$this->runTestSet($this->tests, 'pathinfo');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user