first commit
This commit is contained in:
30
system/src/Grav/Framework/Cache/AbstractCache.php
Normal file
30
system/src/Grav/Framework/Cache/AbstractCache.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache;
|
||||
|
||||
use Grav\Framework\Cache\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Cache trait for PSR-16 compatible "Simple Cache" implementation
|
||||
* @package Grav\Framework\Cache
|
||||
*/
|
||||
abstract class AbstractCache implements CacheInterface
|
||||
{
|
||||
use CacheTrait;
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @param null|int|\DateInterval $defaultLifetime
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($namespace = '', $defaultLifetime = null)
|
||||
{
|
||||
$this->init($namespace, $defaultLifetime);
|
||||
}
|
||||
}
|
197
system/src/Grav/Framework/Cache/Adapter/ChainCache.php
Normal file
197
system/src/Grav/Framework/Cache/Adapter/ChainCache.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache\Adapter;
|
||||
|
||||
use Grav\Framework\Cache\AbstractCache;
|
||||
use Grav\Framework\Cache\CacheInterface;
|
||||
use Grav\Framework\Cache\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Cache class for PSR-16 compatible "Simple Cache" implementation using chained cache adapters.
|
||||
*
|
||||
* @package Grav\Framework\Cache
|
||||
*/
|
||||
class ChainCache extends AbstractCache
|
||||
{
|
||||
/**
|
||||
* @var array|CacheInterface[]
|
||||
*/
|
||||
protected $caches;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $count;
|
||||
|
||||
/**
|
||||
* Chain Cache constructor.
|
||||
* @param array $caches
|
||||
* @param null|int|\DateInterval $defaultLifetime
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $caches, $defaultLifetime = null)
|
||||
{
|
||||
parent::__construct('', $defaultLifetime);
|
||||
|
||||
if (!$caches) {
|
||||
throw new InvalidArgumentException('At least one cache must be specified');
|
||||
}
|
||||
|
||||
foreach ($caches as $cache) {
|
||||
if (!$cache instanceof CacheInterface) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
"The class '%s' does not implement the '%s' interface",
|
||||
get_class($cache),
|
||||
CacheInterface::class
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->caches = array_values($caches);
|
||||
$this->count = count($caches);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doGet($key, $miss)
|
||||
{
|
||||
foreach ($this->caches as $i => $cache) {
|
||||
$value = $cache->doGet($key, $miss);
|
||||
if ($value !== $miss) {
|
||||
while (--$i >= 0) {
|
||||
// Update all the previous caches with missing value.
|
||||
$this->caches[$i]->doSet($key, $value, $this->getDefaultLifetime());
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $miss;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doSet($key, $value, $ttl)
|
||||
{
|
||||
$success = true;
|
||||
$i = $this->count;
|
||||
|
||||
while ($i--) {
|
||||
$success = $this->caches[$i]->doSet($key, $value, $ttl) && $success;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doDelete($key)
|
||||
{
|
||||
$success = true;
|
||||
$i = $this->count;
|
||||
|
||||
while ($i--) {
|
||||
$success = $this->caches[$i]->doDelete($key) && $success;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doClear()
|
||||
{
|
||||
$success = true;
|
||||
$i = $this->count;
|
||||
|
||||
while ($i--) {
|
||||
$success = $this->caches[$i]->doClear() && $success;
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doGetMultiple($keys, $miss)
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->caches as $i => $cache) {
|
||||
$list[$i] = $cache->doGetMultiple($keys, $miss);
|
||||
|
||||
$keys = array_diff_key($keys, $list[$i]);
|
||||
|
||||
if (!$keys) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$values = [];
|
||||
// Update all the previous caches with missing values.
|
||||
foreach (array_reverse($list) as $i => $items) {
|
||||
$values += $items;
|
||||
if ($i && $values) {
|
||||
$this->caches[$i-1]->doSetMultiple($values, $this->getDefaultLifetime());
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doSetMultiple($values, $ttl)
|
||||
{
|
||||
$success = true;
|
||||
$i = $this->count;
|
||||
|
||||
while ($i--) {
|
||||
$success = $this->caches[$i]->doSetMultiple($values, $ttl) && $success;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doDeleteMultiple($keys)
|
||||
{
|
||||
$success = true;
|
||||
$i = $this->count;
|
||||
|
||||
while ($i--) {
|
||||
$success = $this->caches[$i]->doDeleteMultiple($keys) && $success;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doHas($key)
|
||||
{
|
||||
foreach ($this->caches as $cache) {
|
||||
if ($cache->doHas($key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
123
system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php
Normal file
123
system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache\Adapter;
|
||||
|
||||
use Doctrine\Common\Cache\CacheProvider;
|
||||
use Grav\Framework\Cache\AbstractCache;
|
||||
use Grav\Framework\Cache\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Cache class for PSR-16 compatible "Simple Cache" implementation using Doctrine Cache backend.
|
||||
* @package Grav\Framework\Cache
|
||||
*/
|
||||
class DoctrineCache extends AbstractCache
|
||||
{
|
||||
/**
|
||||
* @var CacheProvider
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* Doctrine Cache constructor.
|
||||
*
|
||||
* @param CacheProvider $doctrineCache
|
||||
* @param string $namespace
|
||||
* @param null|int|\DateInterval $defaultLifetime
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function __construct(CacheProvider $doctrineCache, $namespace = '', $defaultLifetime = null)
|
||||
{
|
||||
// Do not use $namespace or $defaultLifetime directly, store them with constructor and fetch with methods.
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
|
||||
// Set namespace to Doctrine Cache provider if it was given.
|
||||
$namespace = $this->getNamespace();
|
||||
$namespace && $doctrineCache->setNamespace($namespace);
|
||||
|
||||
$this->driver = $doctrineCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doGet($key, $miss)
|
||||
{
|
||||
$value = $this->driver->fetch($key);
|
||||
|
||||
// Doctrine cache does not differentiate between no result and cached 'false'. Make sure that we do.
|
||||
return $value !== false || $this->driver->contains($key) ? $value : $miss;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doSet($key, $value, $ttl)
|
||||
{
|
||||
return $this->driver->save($key, $value, (int) $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doDelete($key)
|
||||
{
|
||||
return $this->driver->delete($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doClear()
|
||||
{
|
||||
return $this->driver->deleteAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doGetMultiple($keys, $miss)
|
||||
{
|
||||
return $this->driver->fetchMultiple($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doSetMultiple($values, $ttl)
|
||||
{
|
||||
return $this->driver->saveMultiple($values, (int) $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function doDeleteMultiple($keys)
|
||||
{
|
||||
// TODO: Remove when Doctrine Cache has been updated to support the feature.
|
||||
if (!method_exists($this->driver, 'deleteMultiple')) {
|
||||
$success = true;
|
||||
foreach ($keys as $key) {
|
||||
$success = $this->delete($key) && $success;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
return $this->driver->deleteMultiple($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doHas($key)
|
||||
{
|
||||
return $this->driver->contains($key);
|
||||
}
|
||||
}
|
210
system/src/Grav/Framework/Cache/Adapter/FileCache.php
Normal file
210
system/src/Grav/Framework/Cache/Adapter/FileCache.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache\Adapter;
|
||||
|
||||
use Grav\Framework\Cache\AbstractCache;
|
||||
use Grav\Framework\Cache\Exception\CacheException;
|
||||
use Grav\Framework\Cache\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Cache class for PSR-16 compatible "Simple Cache" implementation using file backend.
|
||||
*
|
||||
* Defaults to 1 year TTL. Does not support unlimited TTL.
|
||||
*
|
||||
* @package Grav\Framework\Cache
|
||||
*/
|
||||
class FileCache extends AbstractCache
|
||||
{
|
||||
private $directory;
|
||||
private $tmp;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct($namespace = '', $defaultLifetime = null, $folder = null)
|
||||
{
|
||||
parent::__construct($namespace, $defaultLifetime ?: 31557600); // = 1 year
|
||||
|
||||
$this->initFileCache($namespace, $folder ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doGet($key, $miss)
|
||||
{
|
||||
$now = time();
|
||||
$file = $this->getFile($key);
|
||||
|
||||
if (!file_exists($file) || !$h = @fopen($file, 'rb')) {
|
||||
return $miss;
|
||||
}
|
||||
|
||||
if ($now >= (int) $expiresAt = fgets($h)) {
|
||||
fclose($h);
|
||||
@unlink($file);
|
||||
} else {
|
||||
$i = rawurldecode(rtrim(fgets($h)));
|
||||
$value = stream_get_contents($h);
|
||||
fclose($h);
|
||||
|
||||
if ($i === $key) {
|
||||
return unserialize($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $miss;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Psr\SimpleCache\CacheException
|
||||
*/
|
||||
public function doSet($key, $value, $ttl)
|
||||
{
|
||||
$expiresAt = time() + (int)$ttl;
|
||||
|
||||
$result = $this->write(
|
||||
$this->getFile($key, true),
|
||||
$expiresAt . "\n" . rawurlencode($key) . "\n" . serialize($value),
|
||||
$expiresAt
|
||||
);
|
||||
|
||||
if (!$result && !is_writable($this->directory)) {
|
||||
throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doDelete($key)
|
||||
{
|
||||
$file = $this->getFile($key);
|
||||
|
||||
return (!file_exists($file) || @unlink($file) || !file_exists($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doClear()
|
||||
{
|
||||
$result = true;
|
||||
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS));
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
$result = ($file->isDir() || @unlink($file) || !file_exists($file)) && $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function doHas($key)
|
||||
{
|
||||
$file = $this->getFile($key);
|
||||
|
||||
return file_exists($file) && (@filemtime($file) > time() || $this->doGet($key, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param bool $mkdir
|
||||
* @return string
|
||||
*/
|
||||
protected function getFile($key, $mkdir = false)
|
||||
{
|
||||
$hash = str_replace('/', '-', base64_encode(hash('sha256', static::class . $key, true)));
|
||||
$dir = $this->directory . $hash[0] . DIRECTORY_SEPARATOR . $hash[1] . DIRECTORY_SEPARATOR;
|
||||
|
||||
if ($mkdir && !file_exists($dir)) {
|
||||
@mkdir($dir, 0777, true);
|
||||
}
|
||||
|
||||
return $dir . substr($hash, 2, 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespace
|
||||
* @param string $directory
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException|InvalidArgumentException
|
||||
*/
|
||||
protected function initFileCache($namespace, $directory)
|
||||
{
|
||||
if (!isset($directory[0])) {
|
||||
$directory = sys_get_temp_dir() . '/grav-cache';
|
||||
} else {
|
||||
$directory = realpath($directory) ?: $directory;
|
||||
}
|
||||
|
||||
if (isset($namespace[0])) {
|
||||
if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
|
||||
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
|
||||
}
|
||||
$directory .= DIRECTORY_SEPARATOR . $namespace;
|
||||
}
|
||||
|
||||
$this->mkdir($directory);
|
||||
|
||||
$directory .= DIRECTORY_SEPARATOR;
|
||||
// On Windows the whole path is limited to 258 chars
|
||||
if ('\\' === DIRECTORY_SEPARATOR && strlen($directory) > 234) {
|
||||
throw new InvalidArgumentException(sprintf('Cache folder is too long (%s)', $directory));
|
||||
}
|
||||
$this->directory = $directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param string $data
|
||||
* @param int|null $expiresAt
|
||||
* @return bool
|
||||
*/
|
||||
private function write($file, $data, $expiresAt = null)
|
||||
{
|
||||
set_error_handler(__CLASS__.'::throwError');
|
||||
|
||||
try {
|
||||
if ($this->tmp === null) {
|
||||
$this->tmp = $this->directory . uniqid('', true);
|
||||
}
|
||||
|
||||
file_put_contents($this->tmp, $data);
|
||||
|
||||
if ($expiresAt !== null) {
|
||||
touch($this->tmp, $expiresAt);
|
||||
}
|
||||
|
||||
return rename($this->tmp, $file);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @throws \ErrorException
|
||||
*/
|
||||
public static function throwError($type, $message, $file, $line)
|
||||
{
|
||||
throw new \ErrorException($message, 0, $type, $file, $line);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->tmp !== null && file_exists($this->tmp)) {
|
||||
unlink($this->tmp);
|
||||
}
|
||||
}
|
||||
}
|
61
system/src/Grav/Framework/Cache/Adapter/MemoryCache.php
Normal file
61
system/src/Grav/Framework/Cache/Adapter/MemoryCache.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache\Adapter;
|
||||
|
||||
use Grav\Framework\Cache\AbstractCache;
|
||||
|
||||
/**
|
||||
* Cache class for PSR-16 compatible "Simple Cache" implementation using in memory backend.
|
||||
*
|
||||
* Memory backend does not use namespace or default ttl as the cache is unique to each cache object and request.
|
||||
*
|
||||
* @package Grav\Framework\Cache
|
||||
*/
|
||||
class MemoryCache extends AbstractCache
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $cache = [];
|
||||
|
||||
public function doGet($key, $miss)
|
||||
{
|
||||
if (!array_key_exists($key, $this->cache)) {
|
||||
return $miss;
|
||||
}
|
||||
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
public function doSet($key, $value, $ttl)
|
||||
{
|
||||
$this->cache[$key] = $value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function doDelete($key)
|
||||
{
|
||||
unset($this->cache[$key]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function doClear()
|
||||
{
|
||||
$this->cache = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function doHas($key)
|
||||
{
|
||||
return array_key_exists($key, $this->cache);
|
||||
}
|
||||
}
|
78
system/src/Grav/Framework/Cache/Adapter/SessionCache.php
Normal file
78
system/src/Grav/Framework/Cache/Adapter/SessionCache.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache\Adapter;
|
||||
|
||||
use Grav\Framework\Cache\AbstractCache;
|
||||
|
||||
/**
|
||||
* Cache class for PSR-16 compatible "Simple Cache" implementation using session backend.
|
||||
*
|
||||
* @package Grav\Framework\Cache
|
||||
*/
|
||||
class SessionCache extends AbstractCache
|
||||
{
|
||||
const VALUE = 0;
|
||||
const LIFETIME = 1;
|
||||
|
||||
public function doGet($key, $miss)
|
||||
{
|
||||
$stored = $this->doGetStored($key);
|
||||
|
||||
return $stored ? $stored[self::VALUE] : $miss;
|
||||
}
|
||||
|
||||
public function doSet($key, $value, $ttl)
|
||||
{
|
||||
$stored = [self::VALUE => $value];
|
||||
if (null !== $ttl) {
|
||||
$stored[self::LIFETIME] = time() + $ttl;
|
||||
|
||||
}
|
||||
|
||||
$_SESSION[$this->getNamespace()][$key] = $stored;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function doDelete($key)
|
||||
{
|
||||
unset($_SESSION[$this->getNamespace()][$key]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function doClear()
|
||||
{
|
||||
unset($_SESSION[$this->getNamespace()]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function doHas($key)
|
||||
{
|
||||
return $this->doGetStored($key) !== null;
|
||||
}
|
||||
|
||||
public function getNamespace()
|
||||
{
|
||||
return 'cache-' . parent::getNamespace();
|
||||
}
|
||||
|
||||
protected function doGetStored($key)
|
||||
{
|
||||
$stored = isset($_SESSION[$this->getNamespace()][$key]) ? $_SESSION[$this->getNamespace()][$key] : null;
|
||||
|
||||
if (isset($stored[self::LIFETIME]) && $stored[self::LIFETIME] < time()) {
|
||||
unset($_SESSION[$this->getNamespace()][$key]);
|
||||
$stored = null;
|
||||
}
|
||||
|
||||
return $stored ?: null;
|
||||
}
|
||||
}
|
27
system/src/Grav/Framework/Cache/CacheInterface.php
Normal file
27
system/src/Grav/Framework/Cache/CacheInterface.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface as SimpleCacheInterface;
|
||||
|
||||
/**
|
||||
* PSR-16 compatible "Simple Cache" interface.
|
||||
* @package Grav\Framework\Object\Storage
|
||||
*/
|
||||
interface CacheInterface extends SimpleCacheInterface
|
||||
{
|
||||
public function doGet($key, $miss);
|
||||
public function doSet($key, $value, $ttl);
|
||||
public function doDelete($key);
|
||||
public function doClear();
|
||||
public function doGetMultiple($keys, $miss);
|
||||
public function doSetMultiple($values, $ttl);
|
||||
public function doDeleteMultiple($keys);
|
||||
public function doHas($key);
|
||||
}
|
350
system/src/Grav/Framework/Cache/CacheTrait.php
Normal file
350
system/src/Grav/Framework/Cache/CacheTrait.php
Normal file
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache;
|
||||
|
||||
use Grav\Framework\Cache\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Cache trait for PSR-16 compatible "Simple Cache" implementation
|
||||
* @package Grav\Framework\Cache
|
||||
*/
|
||||
trait CacheTrait
|
||||
{
|
||||
/** @var string */
|
||||
private $namespace = '';
|
||||
|
||||
/** @var int|null */
|
||||
private $defaultLifetime = null;
|
||||
|
||||
/** @var \stdClass */
|
||||
private $miss;
|
||||
|
||||
/** @var bool */
|
||||
private $validation = true;
|
||||
|
||||
/**
|
||||
* Always call from constructor.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @param null|int|\DateInterval $defaultLifetime
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
protected function init($namespace = '', $defaultLifetime = null)
|
||||
{
|
||||
$this->namespace = (string) $namespace;
|
||||
$this->defaultLifetime = $this->convertTtl($defaultLifetime);
|
||||
$this->miss = new \stdClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $validation
|
||||
*/
|
||||
public function setValidation($validation)
|
||||
{
|
||||
$this->validation = (bool) $validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getNamespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getDefaultLifetime()
|
||||
{
|
||||
return $this->defaultLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
$this->validateKey($key);
|
||||
|
||||
$value = $this->doGet($key, $this->miss);
|
||||
|
||||
return $value !== $this->miss ? $value : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
$this->validateKey($key);
|
||||
|
||||
$ttl = $this->convertTtl($ttl);
|
||||
|
||||
// If a negative or zero TTL is provided, the item MUST be deleted from the cache.
|
||||
return null !== $ttl && $ttl <= 0 ? $this->doDelete($key) : $this->doSet($key, $value, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
$this->validateKey($key);
|
||||
|
||||
return $this->doDelete($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return $this->doClear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function getMultiple($keys, $default = null)
|
||||
{
|
||||
if ($keys instanceof \Traversable) {
|
||||
$keys = iterator_to_array($keys, false);
|
||||
} elseif (!is_array($keys)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Cache keys must be array or Traversable, "%s" given',
|
||||
is_object($keys) ? get_class($keys) : gettype($keys)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($keys)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->validateKeys($keys);
|
||||
$keys = array_unique($keys);
|
||||
$keys = array_combine($keys, $keys);
|
||||
|
||||
$list = $this->doGetMultiple($keys, $this->miss);
|
||||
|
||||
// Make sure that values are returned in the same order as the keys were given.
|
||||
$values = [];
|
||||
foreach ($keys as $key) {
|
||||
if (!array_key_exists($key, $list) || $list[$key] === $this->miss) {
|
||||
$values[$key] = $default;
|
||||
} else {
|
||||
$values[$key] = $list[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function setMultiple($values, $ttl = null)
|
||||
{
|
||||
if ($values instanceof \Traversable) {
|
||||
$values = iterator_to_array($values, true);
|
||||
} elseif (!is_array($values)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Cache values must be array or Traversable, "%s" given',
|
||||
is_object($values) ? get_class($values) : gettype($values)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$keys = array_keys($values);
|
||||
|
||||
if (empty($keys)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->validateKeys($keys);
|
||||
|
||||
$ttl = $this->convertTtl($ttl);
|
||||
|
||||
// If a negative or zero TTL is provided, the item MUST be deleted from the cache.
|
||||
return null !== $ttl && $ttl <= 0 ? $this->doDeleteMultiple($keys) : $this->doSetMultiple($values, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function deleteMultiple($keys)
|
||||
{
|
||||
if ($keys instanceof \Traversable) {
|
||||
$keys = iterator_to_array($keys, false);
|
||||
} elseif (!is_array($keys)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Cache keys must be array or Traversable, "%s" given',
|
||||
is_object($keys) ? get_class($keys) : gettype($keys)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($keys)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->validateKeys($keys);
|
||||
|
||||
return $this->doDeleteMultiple($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
$this->validateKey($key);
|
||||
|
||||
return $this->doHas($key);
|
||||
}
|
||||
|
||||
abstract public function doGet($key, $miss);
|
||||
abstract public function doSet($key, $value, $ttl);
|
||||
abstract public function doDelete($key);
|
||||
abstract public function doClear();
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @param mixed $miss
|
||||
* @return array
|
||||
*/
|
||||
public function doGetMultiple($keys, $miss)
|
||||
{
|
||||
$results = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$value = $this->doGet($key, $miss);
|
||||
if ($value !== $miss) {
|
||||
$results[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $values
|
||||
* @param int $ttl
|
||||
* @return bool
|
||||
*/
|
||||
public function doSetMultiple($values, $ttl)
|
||||
{
|
||||
$success = true;
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
$success = $this->doSet($key, $value, $ttl) && $success;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return bool
|
||||
*/
|
||||
public function doDeleteMultiple($keys)
|
||||
{
|
||||
$success = true;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$success = $this->doDelete($key) && $success;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
abstract public function doHas($key);
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
protected function validateKey($key)
|
||||
{
|
||||
if (!is_string($key)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Cache key must be string, "%s" given',
|
||||
is_object($key) ? get_class($key) : gettype($key)
|
||||
)
|
||||
);
|
||||
}
|
||||
if (!isset($key[0])) {
|
||||
throw new InvalidArgumentException('Cache key length must be greater than zero');
|
||||
}
|
||||
if (strlen($key) > 64) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf('Cache key length must be less than 65 characters, key had %s characters', strlen($key))
|
||||
);
|
||||
}
|
||||
if (strpbrk($key, '{}()/\@:') !== false) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
protected function validateKeys($keys)
|
||||
{
|
||||
if (!$this->validation) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$this->validateKey($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|int|\DateInterval $ttl
|
||||
* @return int|null
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
protected function convertTtl($ttl)
|
||||
{
|
||||
if ($ttl === null) {
|
||||
return $this->getDefaultLifetime();
|
||||
}
|
||||
|
||||
if (is_int($ttl)) {
|
||||
return $ttl;
|
||||
}
|
||||
|
||||
if ($ttl instanceof \DateInterval) {
|
||||
$ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Expiration date must be an integer, a DateInterval or null, "%s" given',
|
||||
is_object($ttl) ? get_class($ttl) : gettype($ttl)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
19
system/src/Grav/Framework/Cache/Exception/CacheException.php
Normal file
19
system/src/Grav/Framework/Cache/Exception/CacheException.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache\Exception;
|
||||
|
||||
use Psr\SimpleCache\CacheException as SimpleCacheException;
|
||||
|
||||
/**
|
||||
* CacheException class for PSR-16 compatible "Simple Cache" implementation.
|
||||
* @package Grav\Framework\Cache\Exception
|
||||
*/
|
||||
class CacheException extends \Exception implements SimpleCacheException
|
||||
{
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Cache
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Cache\Exception;
|
||||
|
||||
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentException;
|
||||
|
||||
/**
|
||||
* InvalidArgumentException class for PSR-16 compatible "Simple Cache" implementation.
|
||||
* @package Grav\Framework\Cache\Exception
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentException
|
||||
{
|
||||
}
|
227
system/src/Grav/Framework/Collection/AbstractFileCollection.php
Normal file
227
system/src/Grav/Framework/Collection/AbstractFileCollection.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Collection;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\ResourceLocator\RecursiveUniformResourceIterator;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* Collection of objects stored into a filesystem.
|
||||
*
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
class AbstractFileCollection extends AbstractLazyCollection implements FileCollectionInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var \RecursiveDirectoryIterator|RecursiveUniformResourceIterator
|
||||
*/
|
||||
protected $iterator;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $createObjectFunction;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $filterFunction;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $flags;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $nestingLimit;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*/
|
||||
protected function __construct($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->flags = self::INCLUDE_FILES | self::INCLUDE_FOLDERS;
|
||||
$this->nestingLimit = 0;
|
||||
$this->createObjectFunction = [$this, 'createObject'];
|
||||
|
||||
$this->setIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Criteria $criteria
|
||||
* @return ArrayCollection
|
||||
* @todo Implement lazy matching
|
||||
*/
|
||||
public function matching(Criteria $criteria)
|
||||
{
|
||||
$expr = $criteria->getWhereExpression();
|
||||
|
||||
$oldFilter = $this->filterFunction;
|
||||
if ($expr) {
|
||||
$visitor = new ClosureExpressionVisitor();
|
||||
$filter = $visitor->dispatch($expr);
|
||||
$this->addFilter($filter);
|
||||
}
|
||||
|
||||
$filtered = $this->doInitializeByIterator($this->iterator, $this->nestingLimit);
|
||||
$this->filterFunction = $oldFilter;
|
||||
|
||||
if ($orderings = $criteria->getOrderings()) {
|
||||
$next = null;
|
||||
foreach (array_reverse($orderings) as $field => $ordering) {
|
||||
$next = ClosureExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next);
|
||||
}
|
||||
|
||||
uasort($filtered, $next);
|
||||
} else {
|
||||
ksort($filtered);
|
||||
}
|
||||
|
||||
$offset = $criteria->getFirstResult();
|
||||
$length = $criteria->getMaxResults();
|
||||
|
||||
if ($offset || $length) {
|
||||
$filtered = array_slice($filtered, (int)$offset, $length);
|
||||
}
|
||||
|
||||
return new ArrayCollection($filtered);
|
||||
}
|
||||
|
||||
protected function setIterator()
|
||||
{
|
||||
$iteratorFlags = \RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS
|
||||
+ \FilesystemIterator::CURRENT_AS_SELF + \FilesystemIterator::FOLLOW_SYMLINKS;
|
||||
|
||||
if (strpos($this->path, '://')) {
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
$this->iterator = $locator->getRecursiveIterator($this->path, $iteratorFlags);
|
||||
} else {
|
||||
$this->iterator = new \RecursiveDirectoryIterator($this->path, $iteratorFlags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $filterFunction
|
||||
* @return $this
|
||||
*/
|
||||
protected function addFilter(callable $filterFunction)
|
||||
{
|
||||
if ($this->filterFunction) {
|
||||
$oldFilterFunction = $this->filterFunction;
|
||||
$this->filterFunction = function ($expr) use ($oldFilterFunction, $filterFunction) {
|
||||
return $oldFilterFunction($expr) && $filterFunction($expr);
|
||||
};
|
||||
} else {
|
||||
$this->filterFunction = $filterFunction;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function doInitialize()
|
||||
{
|
||||
$filtered = $this->doInitializeByIterator($this->iterator, $this->nestingLimit);
|
||||
ksort($filtered);
|
||||
|
||||
$this->collection = new ArrayCollection($filtered);
|
||||
}
|
||||
|
||||
protected function doInitializeByIterator(\SeekableIterator $iterator, $nestingLimit)
|
||||
{
|
||||
$children = [];
|
||||
$objects = [];
|
||||
$filter = $this->filterFunction;
|
||||
$objectFunction = $this->createObjectFunction;
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
// Skip files if they shouldn't be included.
|
||||
if (!($this->flags & static::INCLUDE_FILES) && $file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply main filter.
|
||||
if ($filter && !$filter($file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Include children if the recursive flag is set.
|
||||
if (($this->flags & static::RECURSIVE) && $nestingLimit > 0 && $file->hasChildren()) {
|
||||
$children[] = $file->getChildren();
|
||||
}
|
||||
|
||||
// Skip folders if they shouldn't be included.
|
||||
if (!($this->flags & static::INCLUDE_FOLDERS) && $file->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$object = $objectFunction($file);
|
||||
$objects[$object->key] = $object;
|
||||
}
|
||||
|
||||
if ($children) {
|
||||
$objects += $this->doInitializeChildren($children, $nestingLimit - 1);
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \RecursiveDirectoryIterator[] $children
|
||||
* @return array
|
||||
*/
|
||||
protected function doInitializeChildren(array $children, $nestingLimit)
|
||||
{
|
||||
$objects = [];
|
||||
|
||||
foreach ($children as $iterator) {
|
||||
$objects += $this->doInitializeByIterator($iterator, $nestingLimit);
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \RecursiveDirectoryIterator $file
|
||||
* @return object
|
||||
*/
|
||||
protected function createObject($file)
|
||||
{
|
||||
return (object) [
|
||||
'key' => $file->getSubPathname(),
|
||||
'type' => $file->isDir() ? 'folder' : 'file:' . $file->getExtension(),
|
||||
'url' => method_exists($file, 'getUrl') ? $file->getUrl() : null,
|
||||
'pathname' => $file->getPathname(),
|
||||
'mtime' => $file->getMTime()
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Collection;
|
||||
|
||||
use Doctrine\Common\Collections\AbstractLazyCollection as BaseAbstractLazyCollection;
|
||||
|
||||
/**
|
||||
* General JSON serializable collection.
|
||||
*
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
abstract class AbstractLazyCollection extends BaseAbstractLazyCollection implements CollectionInterface
|
||||
{
|
||||
/**
|
||||
* The backed collection to use
|
||||
*
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
protected $collection;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function reverse()
|
||||
{
|
||||
$this->initialize();
|
||||
return $this->collection->reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function shuffle()
|
||||
{
|
||||
$this->initialize();
|
||||
return $this->collection->shuffle();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function chunk($size)
|
||||
{
|
||||
$this->initialize();
|
||||
return $this->collection->chunk($size);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
$this->initialize();
|
||||
return $this->collection->jsonSerialize();
|
||||
}
|
||||
}
|
63
system/src/Grav/Framework/Collection/ArrayCollection.php
Normal file
63
system/src/Grav/Framework/Collection/ArrayCollection.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Collection;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection as BaseArrayCollection;
|
||||
|
||||
/**
|
||||
* General JSON serializable collection.
|
||||
*
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
class ArrayCollection extends BaseArrayCollection implements CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Reverse the order of the items.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function reverse()
|
||||
{
|
||||
return $this->createFrom(array_reverse($this->toArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffle items.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function shuffle()
|
||||
{
|
||||
$keys = $this->getKeys();
|
||||
shuffle($keys);
|
||||
|
||||
return $this->createFrom(array_replace(array_flip($keys), $this->toArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Split collection into chunks.
|
||||
*
|
||||
* @param int $size Size of each chunk.
|
||||
* @return array
|
||||
*/
|
||||
public function chunk($size)
|
||||
{
|
||||
return array_chunk($this->toArray(), $size, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementes JsonSerializable interface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
41
system/src/Grav/Framework/Collection/CollectionInterface.php
Normal file
41
system/src/Grav/Framework/Collection/CollectionInterface.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Collection;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* Collection Interface.
|
||||
*
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
interface CollectionInterface extends Collection, \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Reverse the order of the items.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function reverse();
|
||||
|
||||
/**
|
||||
* Shuffle items.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function shuffle();
|
||||
|
||||
/**
|
||||
* Split collection into chunks.
|
||||
*
|
||||
* @param int $size Size of each chunk.
|
||||
* @return array
|
||||
*/
|
||||
public function chunk($size);
|
||||
}
|
93
system/src/Grav/Framework/Collection/FileCollection.php
Normal file
93
system/src/Grav/Framework/Collection/FileCollection.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Collection;
|
||||
|
||||
/**
|
||||
* Collection of objects stored into a filesystem.
|
||||
*
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
class FileCollection extends AbstractFileCollection
|
||||
{
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $flags
|
||||
*/
|
||||
public function __construct($path, $flags = null)
|
||||
{
|
||||
parent::__construct($path);
|
||||
|
||||
$this->flags = (int)($flags ?: self::INCLUDE_FILES | self::INCLUDE_FOLDERS | self::RECURSIVE);
|
||||
|
||||
$this->setIterator();
|
||||
$this->setFilter();
|
||||
$this->setObjectBuilder();
|
||||
$this->setNestingLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFlags()
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getNestingLimit()
|
||||
{
|
||||
return $this->nestingLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return $this
|
||||
*/
|
||||
public function setNestingLimit($limit = 99)
|
||||
{
|
||||
$this->nestingLimit = (int) $limit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $filterFunction
|
||||
* @return $this
|
||||
*/
|
||||
public function setFilter(callable $filterFunction = null)
|
||||
{
|
||||
$this->filterFunction = $filterFunction;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $filterFunction
|
||||
* @return $this
|
||||
*/
|
||||
public function addFilter(callable $filterFunction)
|
||||
{
|
||||
parent::addFilter($filterFunction);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $objectFunction
|
||||
* @return $this
|
||||
*/
|
||||
public function setObjectBuilder(callable $objectFunction = null)
|
||||
{
|
||||
$this->createObjectFunction = $objectFunction ?: [$this, 'createObject'];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Collection;
|
||||
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
|
||||
/**
|
||||
* Collection of objects stored into a filesystem.
|
||||
*
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
interface FileCollectionInterface extends CollectionInterface, Selectable
|
||||
{
|
||||
const INCLUDE_FILES = 1;
|
||||
const INCLUDE_FOLDERS = 2;
|
||||
const RECURSIVE = 4;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath();
|
||||
}
|
256
system/src/Grav/Framework/ContentBlock/ContentBlock.php
Normal file
256
system/src/Grav/Framework/ContentBlock/ContentBlock.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\ContentBlock;
|
||||
|
||||
/**
|
||||
* Class to create nested blocks of content.
|
||||
*
|
||||
* $innerBlock = ContentBlock::create();
|
||||
* $innerBlock->setContent('my inner content');
|
||||
* $outerBlock = ContentBlock::create();
|
||||
* $outerBlock->setContent(sprintf('Inside my outer block I have %s.', $innerBlock->getToken()));
|
||||
* $outerBlock->addBlock($innerBlock);
|
||||
* echo $outerBlock;
|
||||
*
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*/
|
||||
class ContentBlock implements ContentBlockInterface
|
||||
{
|
||||
protected $version = 1;
|
||||
protected $id;
|
||||
protected $tokenTemplate = '@@BLOCK-%s@@';
|
||||
protected $content = '';
|
||||
protected $blocks = [];
|
||||
protected $checksum;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @return static
|
||||
*/
|
||||
public static function create($id = null)
|
||||
{
|
||||
return new static($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $serialized
|
||||
* @return ContentBlockInterface
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function fromArray(array $serialized)
|
||||
{
|
||||
try {
|
||||
$type = isset($serialized['_type']) ? $serialized['_type'] : null;
|
||||
$id = isset($serialized['id']) ? $serialized['id'] : null;
|
||||
|
||||
if (!$type || !$id || !is_a($type, 'Grav\Framework\ContentBlock\ContentBlockInterface', true)) {
|
||||
throw new \InvalidArgumentException('Bad data');
|
||||
}
|
||||
|
||||
/** @var ContentBlockInterface $instance */
|
||||
$instance = new $type($id);
|
||||
$instance->build($serialized);
|
||||
} catch (\Exception $e) {
|
||||
throw new \InvalidArgumentException(sprintf('Cannot unserialize Block: %s', $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block constructor.
|
||||
*
|
||||
* @param string $id
|
||||
*/
|
||||
public function __construct($id = null)
|
||||
{
|
||||
$this->id = $id ? (string) $id : $this->generateId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getToken()
|
||||
{
|
||||
return sprintf($this->tokenTemplate, $this->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$blocks = [];
|
||||
/**
|
||||
* @var string $id
|
||||
* @var ContentBlockInterface $block
|
||||
*/
|
||||
foreach ($this->blocks as $block) {
|
||||
$blocks[$block->getId()] = $block->toArray();
|
||||
}
|
||||
|
||||
$array = [
|
||||
'_type' => get_class($this),
|
||||
'_version' => $this->version,
|
||||
'id' => $this->id
|
||||
];
|
||||
|
||||
if ($this->checksum) {
|
||||
$array['checksum'] = $this->checksum;
|
||||
}
|
||||
|
||||
if ($this->content) {
|
||||
$array['content'] = $this->content;
|
||||
}
|
||||
|
||||
if ($blocks) {
|
||||
$array['blocks'] = $blocks;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function toString()
|
||||
{
|
||||
if (!$this->blocks) {
|
||||
return (string) $this->content;
|
||||
}
|
||||
|
||||
$tokens = [];
|
||||
$replacements = [];
|
||||
foreach ($this->blocks as $block) {
|
||||
$tokens[] = $block->getToken();
|
||||
$replacements[] = $block->toString();
|
||||
}
|
||||
|
||||
return str_replace($tokens, $replacements, (string) $this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
try {
|
||||
return $this->toString();
|
||||
} catch (\Exception $e) {
|
||||
return sprintf('Error while rendering block: %s', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $serialized
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function build(array $serialized)
|
||||
{
|
||||
$this->checkVersion($serialized);
|
||||
|
||||
$this->id = isset($serialized['id']) ? $serialized['id'] : $this->generateId();
|
||||
$this->checksum = isset($serialized['checksum']) ? $serialized['checksum'] : null;
|
||||
|
||||
if (isset($serialized['content'])) {
|
||||
$this->setContent($serialized['content']);
|
||||
}
|
||||
|
||||
$blocks = isset($serialized['blocks']) ? (array) $serialized['blocks'] : [];
|
||||
foreach ($blocks as $block) {
|
||||
$this->addBlock(self::fromArray($block));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $checksum
|
||||
* @return $this
|
||||
*/
|
||||
public function setChecksum($checksum)
|
||||
{
|
||||
$this->checksum = $checksum;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getChecksum()
|
||||
{
|
||||
return $this->checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return $this
|
||||
*/
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContentBlockInterface $block
|
||||
* @return $this
|
||||
*/
|
||||
public function addBlock(ContentBlockInterface $block)
|
||||
{
|
||||
$this->blocks[$block->getId()] = $block;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return serialize($this->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $serialized
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
$array = unserialize($serialized);
|
||||
$this->build($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function generateId()
|
||||
{
|
||||
return uniqid('', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $serialized
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function checkVersion(array $serialized)
|
||||
{
|
||||
$version = isset($serialized['_version']) ? (int) $serialized['_version'] : 1;
|
||||
if ($version !== $this->version) {
|
||||
throw new \RuntimeException(sprintf('Unsupported version %s', $version));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\ContentBlock;
|
||||
|
||||
/**
|
||||
* ContentBlock Interface
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*/
|
||||
interface ContentBlockInterface extends \Serializable
|
||||
{
|
||||
/**
|
||||
* @param string $id
|
||||
* @return static
|
||||
*/
|
||||
public static function create($id = null);
|
||||
|
||||
/**
|
||||
* @param array $serialized
|
||||
* @return ContentBlockInterface
|
||||
*/
|
||||
public static function fromArray(array $serialized);
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*/
|
||||
public function __construct($id = null);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getToken();
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function toString();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* @param array $serialized
|
||||
*/
|
||||
public function build(array $serialized);
|
||||
|
||||
/**
|
||||
* @param string $checksum
|
||||
* @return $this
|
||||
*/
|
||||
public function setChecksum($checksum);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getChecksum();
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return $this
|
||||
*/
|
||||
public function setContent($content);
|
||||
|
||||
/**
|
||||
* @param ContentBlockInterface $block
|
||||
* @return $this
|
||||
*/
|
||||
public function addBlock(ContentBlockInterface $block);
|
||||
}
|
386
system/src/Grav/Framework/ContentBlock/HtmlBlock.php
Normal file
386
system/src/Grav/Framework/ContentBlock/HtmlBlock.php
Normal file
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\ContentBlock;
|
||||
|
||||
/**
|
||||
* HtmlBlock
|
||||
*
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*/
|
||||
class HtmlBlock extends ContentBlock implements HtmlBlockInterface
|
||||
{
|
||||
protected $version = 1;
|
||||
protected $frameworks = [];
|
||||
protected $styles = [];
|
||||
protected $scripts = [];
|
||||
protected $html = [];
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
$assets = $this->getAssetsFast();
|
||||
|
||||
$this->sortAssets($assets['styles']);
|
||||
$this->sortAssets($assets['scripts']);
|
||||
$this->sortAssets($assets['html']);
|
||||
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFrameworks()
|
||||
{
|
||||
$assets = $this->getAssetsFast();
|
||||
|
||||
return array_keys($assets['frameworks']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
public function getStyles($location = 'head')
|
||||
{
|
||||
return $this->getAssetsInLocation('styles', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
public function getScripts($location = 'head')
|
||||
{
|
||||
return $this->getAssetsInLocation('scripts', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
public function getHtml($location = 'bottom')
|
||||
{
|
||||
return $this->getAssetsInLocation('html', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$array = parent::toArray();
|
||||
|
||||
if ($this->frameworks) {
|
||||
$array['frameworks'] = $this->frameworks;
|
||||
}
|
||||
if ($this->styles) {
|
||||
$array['styles'] = $this->styles;
|
||||
}
|
||||
if ($this->scripts) {
|
||||
$array['scripts'] = $this->scripts;
|
||||
}
|
||||
if ($this->html) {
|
||||
$array['html'] = $this->html;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $serialized
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function build(array $serialized)
|
||||
{
|
||||
parent::build($serialized);
|
||||
|
||||
$this->frameworks = isset($serialized['frameworks']) ? (array) $serialized['frameworks'] : [];
|
||||
$this->styles = isset($serialized['styles']) ? (array) $serialized['styles'] : [];
|
||||
$this->scripts = isset($serialized['scripts']) ? (array) $serialized['scripts'] : [];
|
||||
$this->html = isset($serialized['html']) ? (array) $serialized['html'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $framework
|
||||
* @return $this
|
||||
*/
|
||||
public function addFramework($framework)
|
||||
{
|
||||
$this->frameworks[$framework] = 1;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $element
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*
|
||||
* @example $block->addStyle('assets/js/my.js');
|
||||
* @example $block->addStyle(['href' => 'assets/js/my.js', 'media' => 'screen']);
|
||||
*/
|
||||
public function addStyle($element, $priority = 0, $location = 'head')
|
||||
{
|
||||
if (!is_array($element)) {
|
||||
$element = ['href' => (string) $element];
|
||||
}
|
||||
if (empty($element['href'])) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($this->styles[$location])) {
|
||||
$this->styles[$location] = [];
|
||||
}
|
||||
|
||||
$id = !empty($element['id']) ? ['id' => (string) $element['id']] : [];
|
||||
$href = $element['href'];
|
||||
$type = !empty($element['type']) ? (string) $element['type'] : 'text/css';
|
||||
$media = !empty($element['media']) ? (string) $element['media'] : null;
|
||||
unset(
|
||||
$element['tag'],
|
||||
$element['id'],
|
||||
$element['rel'],
|
||||
$element['content'],
|
||||
$element['href'],
|
||||
$element['type'],
|
||||
$element['media']
|
||||
);
|
||||
|
||||
$this->styles[$location][md5($href) . sha1($href)] = [
|
||||
':type' => 'file',
|
||||
':priority' => (int) $priority,
|
||||
'href' => $href,
|
||||
'type' => $type,
|
||||
'media' => $media,
|
||||
'element' => $element
|
||||
] + $id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $element
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*/
|
||||
public function addInlineStyle($element, $priority = 0, $location = 'head')
|
||||
{
|
||||
if (!is_array($element)) {
|
||||
$element = ['content' => (string) $element];
|
||||
}
|
||||
if (empty($element['content'])) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($this->styles[$location])) {
|
||||
$this->styles[$location] = [];
|
||||
}
|
||||
|
||||
$content = (string) $element['content'];
|
||||
$type = !empty($element['type']) ? (string) $element['type'] : 'text/css';
|
||||
|
||||
$this->styles[$location][md5($content) . sha1($content)] = [
|
||||
':type' => 'inline',
|
||||
':priority' => (int) $priority,
|
||||
'content' => $content,
|
||||
'type' => $type
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $element
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*/
|
||||
public function addScript($element, $priority = 0, $location = 'head')
|
||||
{
|
||||
if (!is_array($element)) {
|
||||
$element = ['src' => (string) $element];
|
||||
}
|
||||
if (empty($element['src'])) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($this->scripts[$location])) {
|
||||
$this->scripts[$location] = [];
|
||||
}
|
||||
|
||||
$src = $element['src'];
|
||||
$type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript';
|
||||
$defer = isset($element['defer']) ? true : false;
|
||||
$async = isset($element['async']) ? true : false;
|
||||
$handle = !empty($element['handle']) ? (string) $element['handle'] : '';
|
||||
|
||||
$this->scripts[$location][md5($src) . sha1($src)] = [
|
||||
':type' => 'file',
|
||||
':priority' => (int) $priority,
|
||||
'src' => $src,
|
||||
'type' => $type,
|
||||
'defer' => $defer,
|
||||
'async' => $async,
|
||||
'handle' => $handle
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $element
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*/
|
||||
public function addInlineScript($element, $priority = 0, $location = 'head')
|
||||
{
|
||||
if (!is_array($element)) {
|
||||
$element = ['content' => (string) $element];
|
||||
}
|
||||
if (empty($element['content'])) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($this->scripts[$location])) {
|
||||
$this->scripts[$location] = [];
|
||||
}
|
||||
|
||||
$content = (string) $element['content'];
|
||||
$type = !empty($element['type']) ? (string) $element['type'] : 'text/javascript';
|
||||
|
||||
$this->scripts[$location][md5($content) . sha1($content)] = [
|
||||
':type' => 'inline',
|
||||
':priority' => (int) $priority,
|
||||
'content' => $content,
|
||||
'type' => $type
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $html
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*/
|
||||
public function addHtml($html, $priority = 0, $location = 'bottom')
|
||||
{
|
||||
if (empty($html) || !is_string($html)) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($this->html[$location])) {
|
||||
$this->html[$location] = [];
|
||||
}
|
||||
|
||||
$this->html[$location][md5($html) . sha1($html)] = [
|
||||
':priority' => (int) $priority,
|
||||
'html' => $html
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getAssetsFast()
|
||||
{
|
||||
$assets = [
|
||||
'frameworks' => $this->frameworks,
|
||||
'styles' => $this->styles,
|
||||
'scripts' => $this->scripts,
|
||||
'html' => $this->html
|
||||
];
|
||||
|
||||
foreach ($this->blocks as $block) {
|
||||
if ($block instanceof HtmlBlock) {
|
||||
$blockAssets = $block->getAssetsFast();
|
||||
$assets['frameworks'] += $blockAssets['frameworks'];
|
||||
|
||||
foreach ($blockAssets['styles'] as $location => $styles) {
|
||||
if (!isset($assets['styles'][$location])) {
|
||||
$assets['styles'][$location] = $styles;
|
||||
} elseif ($styles) {
|
||||
$assets['styles'][$location] += $styles;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($blockAssets['scripts'] as $location => $scripts) {
|
||||
if (!isset($assets['scripts'][$location])) {
|
||||
$assets['scripts'][$location] = $scripts;
|
||||
} elseif ($scripts) {
|
||||
$assets['scripts'][$location] += $scripts;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($blockAssets['html'] as $location => $htmls) {
|
||||
if (!isset($assets['html'][$location])) {
|
||||
$assets['html'][$location] = $htmls;
|
||||
} elseif ($htmls) {
|
||||
$assets['html'][$location] += $htmls;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
protected function getAssetsInLocation($type, $location)
|
||||
{
|
||||
$assets = $this->getAssetsFast();
|
||||
|
||||
if (empty($assets[$type][$location])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$styles = $assets[$type][$location];
|
||||
$this->sortAssetsInLocation($styles);
|
||||
|
||||
return $styles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $items
|
||||
*/
|
||||
protected function sortAssetsInLocation(array &$items)
|
||||
{
|
||||
$count = 0;
|
||||
foreach ($items as &$item) {
|
||||
$item[':order'] = ++$count;
|
||||
}
|
||||
unset($item);
|
||||
|
||||
uasort(
|
||||
$items,
|
||||
function ($a, $b) {
|
||||
return ($a[':priority'] === $b[':priority'])
|
||||
? $a[':order'] - $b[':order'] : $a[':priority'] - $b[':priority'];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
*/
|
||||
protected function sortAssets(array &$array)
|
||||
{
|
||||
foreach ($array as $location => &$items) {
|
||||
$this->sortAssetsInLocation($items);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\ContentBlock;
|
||||
|
||||
/**
|
||||
* Interface HtmlBlockInterface
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*/
|
||||
interface HtmlBlockInterface extends ContentBlockInterface
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets();
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFrameworks();
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
public function getStyles($location = 'head');
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
public function getScripts($location = 'head');
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
public function getHtml($location = 'bottom');
|
||||
|
||||
/**
|
||||
* @param string $framework
|
||||
* @return $this
|
||||
*/
|
||||
public function addFramework($framework);
|
||||
|
||||
/**
|
||||
* @param string|array $element
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*
|
||||
* @example $block->addStyle('assets/js/my.js');
|
||||
* @example $block->addStyle(['href' => 'assets/js/my.js', 'media' => 'screen']);
|
||||
*/
|
||||
public function addStyle($element, $priority = 0, $location = 'head');
|
||||
|
||||
/**
|
||||
* @param string|array $element
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*/
|
||||
public function addInlineStyle($element, $priority = 0, $location = 'head');
|
||||
|
||||
/**
|
||||
* @param string|array $element
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*/
|
||||
public function addScript($element, $priority = 0, $location = 'head');
|
||||
|
||||
|
||||
/**
|
||||
* @param string|array $element
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*/
|
||||
public function addInlineScript($element, $priority = 0, $location = 'head');
|
||||
|
||||
/**
|
||||
* @param string $html
|
||||
* @param int $priority
|
||||
* @param string $location
|
||||
* @return bool
|
||||
*/
|
||||
public function addHtml($html, $priority = 0, $location = 'bottom');
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* @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;
|
||||
|
||||
interface FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Get default file extension from current formatter (with dot).
|
||||
*
|
||||
* Default file extension is the first defined extension.
|
||||
*
|
||||
* @return string File extension (can be empty).
|
||||
*/
|
||||
public function getDefaultFileExtension();
|
||||
|
||||
/**
|
||||
* Get file extensions supported by current formatter (with dot).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSupportedFileExtensions();
|
||||
|
||||
/**
|
||||
* Encode data into a string.
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function encode($data);
|
||||
|
||||
/**
|
||||
* Decode a string into data.
|
||||
*
|
||||
* @param string $data
|
||||
* @return array
|
||||
*/
|
||||
public function decode($data);
|
||||
}
|
85
system/src/Grav/Framework/File/Formatter/IniFormatter.php
Normal file
85
system/src/Grav/Framework/File/Formatter/IniFormatter.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* @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;
|
||||
|
||||
class IniFormatter implements FormatterInterface
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* IniFormatter constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config + [
|
||||
'file_extension' => '.ini'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
|
||||
*/
|
||||
public function getFileExtension()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getDefaultFileExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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)
|
||||
{
|
||||
$string = '';
|
||||
foreach ($data as $key => $value) {
|
||||
$string .= $key . '="' . preg_replace(
|
||||
['/"/', '/\\\/', "/\t/", "/\n/", "/\r/"],
|
||||
['\"', '\\\\', '\t', '\n', '\r'],
|
||||
$value
|
||||
) . "\"\n";
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
$decoded = @parse_ini_string($data);
|
||||
|
||||
if ($decoded === false) {
|
||||
throw new \RuntimeException('Decoding INI failed');
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
}
|
80
system/src/Grav/Framework/File/Formatter/JsonFormatter.php
Normal file
80
system/src/Grav/Framework/File/Formatter/JsonFormatter.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* @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;
|
||||
|
||||
class JsonFormatter implements FormatterInterface
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config + [
|
||||
'file_extension' => '.json',
|
||||
'encode_options' => 0,
|
||||
'decode_assoc' => true
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
|
||||
*/
|
||||
public function getFileExtension()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getDefaultFileExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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)
|
||||
{
|
||||
$encoded = @json_encode($data, $this->config['encode_options']);
|
||||
|
||||
if ($encoded === false) {
|
||||
throw new \RuntimeException('Encoding JSON failed');
|
||||
}
|
||||
|
||||
return $encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
$decoded = @json_decode($data, $this->config['decode_assoc']);
|
||||
|
||||
if ($decoded === false) {
|
||||
throw new \RuntimeException('Decoding JSON failed');
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
}
|
118
system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php
Normal file
118
system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/**
|
||||
* @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;
|
||||
|
||||
class MarkdownFormatter implements FormatterInterface
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
/** @var FormatterInterface */
|
||||
private $headerFormatter;
|
||||
|
||||
public function __construct(array $config = [], FormatterInterface $headerFormatter = null)
|
||||
{
|
||||
$this->config = $config + [
|
||||
'file_extension' => '.md',
|
||||
'header' => 'header',
|
||||
'body' => 'markdown',
|
||||
'raw' => 'frontmatter',
|
||||
'yaml' => ['inline' => 20]
|
||||
];
|
||||
|
||||
$this->headerFormatter = $headerFormatter ?: new YamlFormatter($this->config['yaml']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
|
||||
*/
|
||||
public function getFileExtension()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getDefaultFileExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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)
|
||||
{
|
||||
$headerVar = $this->config['header'];
|
||||
$bodyVar = $this->config['body'];
|
||||
|
||||
$header = isset($data[$headerVar]) ? (array) $data[$headerVar] : [];
|
||||
$body = isset($data[$bodyVar]) ? (string) $data[$bodyVar] : '';
|
||||
|
||||
// Create Markdown file with YAML header.
|
||||
$encoded = '';
|
||||
if ($header) {
|
||||
$encoded = "---\n" . trim($this->headerFormatter->encode($data['header'])) . "\n---\n\n";
|
||||
}
|
||||
$encoded .= $body;
|
||||
|
||||
// Normalize line endings to Unix style.
|
||||
$encoded = preg_replace("/(\r\n|\r)/", "\n", $encoded);
|
||||
|
||||
return $encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
$headerVar = $this->config['header'];
|
||||
$bodyVar = $this->config['body'];
|
||||
$rawVar = $this->config['raw'];
|
||||
|
||||
$content = [
|
||||
$headerVar => [],
|
||||
$bodyVar => ''
|
||||
];
|
||||
|
||||
$headerRegex = "/^---\n(.+?)\n---\n{0,}(.*)$/uis";
|
||||
|
||||
// Normalize line endings to Unix style.
|
||||
$data = preg_replace("/(\r\n|\r)/", "\n", $data);
|
||||
|
||||
// Parse header.
|
||||
preg_match($headerRegex, ltrim($data), $matches);
|
||||
if(empty($matches)) {
|
||||
$content[$bodyVar] = $data;
|
||||
} else {
|
||||
// Normalize frontmatter.
|
||||
$frontmatter = preg_replace("/\n\t/", "\n ", $matches[1]);
|
||||
if ($rawVar) {
|
||||
$content[$rawVar] = $frontmatter;
|
||||
}
|
||||
$content[$headerVar] = $this->headerFormatter->decode($frontmatter);
|
||||
$content[$bodyVar] = $matches[2];
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* @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;
|
||||
|
||||
class SerializeFormatter implements FormatterInterface
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* IniFormatter constructor.
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config + [
|
||||
'file_extension' => '.ser'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
|
||||
*/
|
||||
public function getFileExtension()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getDefaultFileExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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)
|
||||
{
|
||||
return serialize($this->preserveLines($data, ["\n", "\r"], ['\\n', '\\r']));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
$decoded = @unserialize($data);
|
||||
|
||||
if ($decoded === false) {
|
||||
throw new \RuntimeException('Decoding serialized data failed');
|
||||
}
|
||||
|
||||
return $this->preserveLines($decoded, ['\\n', '\\r'], ["\n", "\r"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preserve new lines, recursive function.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param array $search
|
||||
* @param array $replace
|
||||
* @return mixed
|
||||
*/
|
||||
protected function preserveLines($data, $search, $replace)
|
||||
{
|
||||
if (is_string($data)) {
|
||||
$data = str_replace($search, $replace, $data);
|
||||
} elseif (is_array($data)) {
|
||||
foreach ($data as &$value) {
|
||||
$value = $this->preserveLines($value, $search, $replace);
|
||||
}
|
||||
unset($value);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
105
system/src/Grav/Framework/File/Formatter/YamlFormatter.php
Normal file
105
system/src/Grav/Framework/File/Formatter/YamlFormatter.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* @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;
|
||||
|
||||
use Symfony\Component\Yaml\Exception\DumpException;
|
||||
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
|
||||
{
|
||||
/** @var array */
|
||||
private $config;
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = $config + [
|
||||
'file_extension' => '.yaml',
|
||||
'inline' => 5,
|
||||
'indent' => 2,
|
||||
'native' => true,
|
||||
'compat' => true
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 1.5 Use $formatter->getDefaultFileExtension() instead.
|
||||
*/
|
||||
public function getFileExtension()
|
||||
{
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.5, use getDefaultFileExtension() method instead', E_USER_DEPRECATED);
|
||||
|
||||
return $this->getDefaultFileExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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, $inline = null, $indent = null)
|
||||
{
|
||||
try {
|
||||
return (string) YamlParser::dump(
|
||||
$data,
|
||||
$inline ? (int) $inline : $this->config['inline'],
|
||||
$indent ? (int) $indent : $this->config['indent'],
|
||||
YamlParser::DUMP_EXCEPTION_ON_INVALID_TYPE
|
||||
);
|
||||
} catch (DumpException $e) {
|
||||
throw new \RuntimeException('Encoding YAML failed: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function decode($data)
|
||||
{
|
||||
// Try native PECL YAML PHP extension first if available.
|
||||
if ($this->config['native'] && function_exists('yaml_parse')) {
|
||||
// Safely decode YAML.
|
||||
$saved = @ini_get('yaml.decode_php');
|
||||
@ini_set('yaml.decode_php', 0);
|
||||
$decoded = @yaml_parse($data);
|
||||
@ini_set('yaml.decode_php', $saved);
|
||||
|
||||
if ($decoded !== false) {
|
||||
return (array) $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return (array) YamlParser::parse($data);
|
||||
} catch (ParseException $e) {
|
||||
if ($this->config['compat']) {
|
||||
return (array) FallbackYamlParser::parse($data);
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Decoding YAML failed: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
64
system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php
Normal file
64
system/src/Grav/Framework/Object/Access/ArrayAccessTrait.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Access;
|
||||
|
||||
/**
|
||||
* ArrayAccess Object Trait
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
trait ArrayAccessTrait
|
||||
{
|
||||
/**
|
||||
* Whether or not an offset exists.
|
||||
*
|
||||
* @param mixed $offset An offset to check for.
|
||||
* @return bool Returns TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return $this->hasProperty($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value at specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to retrieve.
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->getProperty($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a value to the specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to assign the value to.
|
||||
* @param mixed $value The value to set.
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->setProperty($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets an offset.
|
||||
*
|
||||
* @param mixed $offset The offset to unset.
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
$this->unsetProperty($offset);
|
||||
}
|
||||
|
||||
abstract public function hasProperty($property);
|
||||
abstract public function getProperty($property, $default = null);
|
||||
abstract public function setProperty($property, $value);
|
||||
abstract public function unsetProperty($property);
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Access;
|
||||
|
||||
/**
|
||||
* Nested ArrayAccess Object Trait
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
trait NestedArrayAccessTrait
|
||||
{
|
||||
/**
|
||||
* Whether or not an offset exists.
|
||||
*
|
||||
* @param mixed $offset An offset to check for.
|
||||
* @return bool Returns TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return $this->hasNestedProperty($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value at specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to retrieve.
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->getNestedProperty($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a value to the specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to assign the value to.
|
||||
* @param mixed $value The value to set.
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->setNestedProperty($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets an offset.
|
||||
*
|
||||
* @param mixed $offset The offset to unset.
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
$this->unsetNestedProperty($offset);
|
||||
}
|
||||
|
||||
abstract public function hasNestedProperty($property, $separator = null);
|
||||
abstract public function getNestedProperty($property, $default = null, $separator = null);
|
||||
abstract public function setNestedProperty($property, $value, $separator = null);
|
||||
abstract public function unsetNestedProperty($property, $separator = null);
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Access;
|
||||
|
||||
use Grav\Framework\Object\Interfaces\NestedObjectInterface;
|
||||
|
||||
/**
|
||||
* Nested Properties Collection Trait
|
||||
* @package Grav\Framework\Object\Properties
|
||||
*/
|
||||
trait NestedPropertyCollectionTrait
|
||||
{
|
||||
/**
|
||||
* @param string $property Object property to be matched.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return array Key/Value pairs of the properties.
|
||||
*/
|
||||
public function hasNestedProperty($property, $separator = null)
|
||||
{
|
||||
$list = [];
|
||||
|
||||
/** @var NestedObjectInterface $element */
|
||||
foreach ($this->getIterator() as $id => $element) {
|
||||
$list[$id] = $element->hasNestedProperty($property, $separator);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if not set.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return array Key/Value pairs of the properties.
|
||||
*/
|
||||
public function getNestedProperty($property, $default = null, $separator = null)
|
||||
{
|
||||
$list = [];
|
||||
|
||||
/** @var NestedObjectInterface $element */
|
||||
foreach ($this->getIterator() as $id => $element) {
|
||||
$list[$id] = $element->getNestedProperty($property, $default, $separator);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $value New value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
*/
|
||||
public function setNestedProperty($property, $value, $separator = null)
|
||||
{
|
||||
/** @var NestedObjectInterface $element */
|
||||
foreach ($this->getIterator() as $element) {
|
||||
$element->setNestedProperty($property, $value, $separator);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
*/
|
||||
public function unsetNestedProperty($property, $separator = null)
|
||||
{
|
||||
/** @var NestedObjectInterface $element */
|
||||
foreach ($this->getIterator() as $element) {
|
||||
$element->unsetNestedProperty($property, $separator);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $default Default value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
*/
|
||||
public function defNestedProperty($property, $default, $separator = null)
|
||||
{
|
||||
/** @var NestedObjectInterface $element */
|
||||
foreach ($this->getIterator() as $element) {
|
||||
$element->defNestedProperty($property, $default, $separator);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group items in the collection by a field.
|
||||
*
|
||||
* @param string $property Object property to be used to make groups.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return array
|
||||
*/
|
||||
public function group($property, $separator = null)
|
||||
{
|
||||
$list = [];
|
||||
|
||||
/** @var NestedObjectInterface $element */
|
||||
foreach ($this->getIterator() as $element) {
|
||||
$list[(string) $element->getNestedProperty($property, null, $separator)][] = $element;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Traversable
|
||||
*/
|
||||
abstract public function getIterator();
|
||||
}
|
182
system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php
Normal file
182
system/src/Grav/Framework/Object/Access/NestedPropertyTrait.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Access;
|
||||
|
||||
use Grav\Framework\Object\Interfaces\ObjectInterface;
|
||||
|
||||
/**
|
||||
* Nested Property Object Trait
|
||||
* @package Grav\Framework\Object\Traits
|
||||
*/
|
||||
trait NestedPropertyTrait
|
||||
{
|
||||
/**
|
||||
* @param string $property Object property name.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return bool True if property has been defined (can be null).
|
||||
*/
|
||||
public function hasNestedProperty($property, $separator = null)
|
||||
{
|
||||
$test = new \stdClass;
|
||||
|
||||
return $this->getNestedProperty($property, $test, $separator) !== $test;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if property has not been set.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return mixed Property value.
|
||||
*/
|
||||
public function getNestedProperty($property, $default = null, $separator = null)
|
||||
{
|
||||
$separator = $separator ?: '.';
|
||||
$path = explode($separator, $property);
|
||||
$offset = array_shift($path);
|
||||
|
||||
if (!$this->hasProperty($offset)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$current = $this->getProperty($offset);
|
||||
|
||||
while ($path) {
|
||||
// Get property of nested Object.
|
||||
if ($current instanceof ObjectInterface) {
|
||||
if (method_exists($current, 'getNestedProperty')) {
|
||||
return $current->getNestedProperty(implode($separator, $path), $default, $separator);
|
||||
}
|
||||
return $current->getProperty(implode($separator, $path), $default);
|
||||
}
|
||||
|
||||
$offset = array_shift($path);
|
||||
|
||||
if ((is_array($current) || is_a($current, 'ArrayAccess')) && isset($current[$offset])) {
|
||||
$current = $current[$offset];
|
||||
} elseif (is_object($current) && isset($current->{$offset})) {
|
||||
$current = $current->{$offset};
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
};
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $value New value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setNestedProperty($property, $value, $separator = null)
|
||||
{
|
||||
$separator = $separator ?: '.';
|
||||
$path = explode($separator, $property);
|
||||
$offset = array_shift($path);
|
||||
|
||||
if (!$path) {
|
||||
$this->setProperty($offset, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$current = &$this->doGetProperty($offset, null, true);
|
||||
|
||||
while ($path) {
|
||||
$offset = array_shift($path);
|
||||
|
||||
// Handle arrays and scalars.
|
||||
if ($current === null) {
|
||||
$current = [$offset => []];
|
||||
} elseif (is_array($current)) {
|
||||
if (!isset($current[$offset])) {
|
||||
$current[$offset] = [];
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException('Cannot set nested property on non-array value');
|
||||
}
|
||||
|
||||
$current = &$current[$offset];
|
||||
};
|
||||
|
||||
$current = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function unsetNestedProperty($property, $separator = null)
|
||||
{
|
||||
$separator = $separator ?: '.';
|
||||
$path = explode($separator, $property);
|
||||
$offset = array_shift($path);
|
||||
|
||||
if (!$path) {
|
||||
$this->unsetProperty($offset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$last = array_pop($path);
|
||||
$current = &$this->doGetProperty($offset, null, true);
|
||||
|
||||
while ($path) {
|
||||
$offset = array_shift($path);
|
||||
|
||||
// Handle arrays and scalars.
|
||||
if ($current === null) {
|
||||
return $this;
|
||||
}
|
||||
if (is_array($current)) {
|
||||
if (!isset($current[$offset])) {
|
||||
return $this;
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException('Cannot set nested property on non-array value');
|
||||
}
|
||||
|
||||
$current = &$current[$offset];
|
||||
};
|
||||
|
||||
unset($current[$last]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $default Default value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function defNestedProperty($property, $default, $separator = null)
|
||||
{
|
||||
if (!$this->hasNestedProperty($property, $separator)) {
|
||||
$this->setNestedProperty($property, $default, $separator);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
abstract public function hasProperty($property);
|
||||
abstract public function getProperty($property, $default = null);
|
||||
abstract public function setProperty($property, $value);
|
||||
abstract public function unsetProperty($property);
|
||||
abstract protected function &doGetProperty($property, $default = null, $doCreate = false);
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Access;
|
||||
|
||||
/**
|
||||
* Overloaded Property Object Trait
|
||||
* @package Grav\Framework\Object\Access
|
||||
*/
|
||||
trait OverloadedPropertyTrait
|
||||
{
|
||||
/**
|
||||
* Checks whether or not an offset exists.
|
||||
*
|
||||
* @param mixed $offset An offset to check for.
|
||||
* @return bool Returns TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function __isset($offset)
|
||||
{
|
||||
return $this->hasProperty($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value at specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to retrieve.
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
public function __get($offset)
|
||||
{
|
||||
return $this->getProperty($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a value to the specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to assign the value to.
|
||||
* @param mixed $value The value to set.
|
||||
*/
|
||||
public function __set($offset, $value)
|
||||
{
|
||||
$this->setProperty($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to unset the attribute
|
||||
*
|
||||
* @param mixed $offset The name value to unset
|
||||
*/
|
||||
public function __unset($offset)
|
||||
{
|
||||
$this->unsetProperty($offset);
|
||||
}
|
||||
|
||||
abstract public function hasProperty($property);
|
||||
abstract public function getProperty($property, $default = null);
|
||||
abstract public function setProperty($property, $value);
|
||||
abstract public function unsetProperty($property);
|
||||
}
|
26
system/src/Grav/Framework/Object/ArrayObject.php
Normal file
26
system/src/Grav/Framework/Object/ArrayObject.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
use Grav\Framework\Object\Access\NestedArrayAccessTrait;
|
||||
use Grav\Framework\Object\Access\NestedPropertyTrait;
|
||||
use Grav\Framework\Object\Access\OverloadedPropertyTrait;
|
||||
use Grav\Framework\Object\Base\ObjectTrait;
|
||||
use Grav\Framework\Object\Interfaces\NestedObjectInterface;
|
||||
use Grav\Framework\Object\Property\ArrayPropertyTrait;
|
||||
|
||||
/**
|
||||
* Array Object class.
|
||||
*
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
class ArrayObject implements NestedObjectInterface, \ArrayAccess
|
||||
{
|
||||
use ObjectTrait, ArrayPropertyTrait, NestedPropertyTrait, OverloadedPropertyTrait, NestedArrayAccessTrait;
|
||||
}
|
180
system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php
Normal file
180
system/src/Grav/Framework/Object/Base/ObjectCollectionTrait.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Base;
|
||||
|
||||
use Grav\Framework\Object\Interfaces\ObjectInterface;
|
||||
|
||||
/**
|
||||
* ObjectCollection Trait
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
trait ObjectCollectionTrait
|
||||
{
|
||||
use ObjectTrait {
|
||||
setKey as public;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy from this collection by cloning all objects in the collection.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->getIterator() as $key => $value) {
|
||||
$list[$key] = is_object($value) ? clone $value : $value;
|
||||
}
|
||||
|
||||
return $this->createFrom($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getObjectKeys()
|
||||
{
|
||||
return $this->call('getKey');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be matched.
|
||||
* @return array Key/Value pairs of the properties.
|
||||
*/
|
||||
public function doHasProperty($property)
|
||||
{
|
||||
$list = [];
|
||||
|
||||
/** @var ObjectInterface $element */
|
||||
foreach ($this->getIterator() as $id => $element) {
|
||||
$list[$id] = $element->hasProperty($property);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if not set.
|
||||
* @return array Key/Value pairs of the properties.
|
||||
*/
|
||||
public function doGetProperty($property, $default = null)
|
||||
{
|
||||
$list = [];
|
||||
|
||||
/** @var ObjectInterface $element */
|
||||
foreach ($this->getIterator() as $id => $element) {
|
||||
$list[$id] = $element->getProperty($property, $default);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $value New value.
|
||||
* @return $this
|
||||
*/
|
||||
public function doSetProperty($property, $value)
|
||||
{
|
||||
/** @var ObjectInterface $element */
|
||||
foreach ($this->getIterator() as $element) {
|
||||
$element->setProperty($property, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @return $this
|
||||
*/
|
||||
public function doUnsetProperty($property)
|
||||
{
|
||||
/** @var ObjectInterface $element */
|
||||
foreach ($this->getIterator() as $element) {
|
||||
$element->unsetProperty($property);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $default Default value.
|
||||
* @return $this
|
||||
*/
|
||||
public function doDefProperty($property, $default)
|
||||
{
|
||||
/** @var ObjectInterface $element */
|
||||
foreach ($this->getIterator() as $element) {
|
||||
$element->defProperty($property, $default);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method Method name.
|
||||
* @param array $arguments List of arguments passed to the function.
|
||||
* @return array Return values.
|
||||
*/
|
||||
public function call($method, array $arguments = [])
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->getIterator() as $id => $element) {
|
||||
$list[$id] = method_exists($element, $method)
|
||||
? call_user_func_array([$element, $method], $arguments) : null;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group items in the collection by a field and return them as associated array.
|
||||
*
|
||||
* @param string $property
|
||||
* @return array
|
||||
*/
|
||||
public function group($property)
|
||||
{
|
||||
$list = [];
|
||||
|
||||
/** @var ObjectInterface $element */
|
||||
foreach ($this->getIterator() as $element) {
|
||||
$list[(string) $element->getProperty($property)][] = $element;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group items in the collection by a field and return them as associated array of collections.
|
||||
*
|
||||
* @param string $property
|
||||
* @return static[]
|
||||
*/
|
||||
public function collectionGroup($property)
|
||||
{
|
||||
$collections = [];
|
||||
foreach ($this->group($property) as $id => $elements) {
|
||||
$collection = $this->createFrom($elements);
|
||||
|
||||
$collections[$id] = $collection;
|
||||
}
|
||||
|
||||
return $collections;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Traversable
|
||||
*/
|
||||
abstract public function getIterator();
|
||||
}
|
195
system/src/Grav/Framework/Object/Base/ObjectTrait.php
Normal file
195
system/src/Grav/Framework/Object/Base/ObjectTrait.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Base;
|
||||
|
||||
/**
|
||||
* Object trait.
|
||||
*
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
trait ObjectTrait
|
||||
{
|
||||
/** @var string */
|
||||
static protected $type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_key;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getTypePrefix()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $prefix
|
||||
* @return string
|
||||
*/
|
||||
public function getType($prefix = true)
|
||||
{
|
||||
$type = $prefix ? $this->getTypePrefix() : '';
|
||||
|
||||
if (static::$type) {
|
||||
return $type . static::$type;
|
||||
}
|
||||
|
||||
$class = get_class($this);
|
||||
return $type . strtolower(substr($class, strrpos($class, '\\') + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->_key ?: $this->getType() . '@' . spl_object_hash($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property name.
|
||||
* @return bool True if property has been defined (can be null).
|
||||
*/
|
||||
public function hasProperty($property)
|
||||
{
|
||||
return $this->doHasProperty($property);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if property has not been set.
|
||||
* @return mixed Property value.
|
||||
*/
|
||||
public function getProperty($property, $default = null)
|
||||
{
|
||||
return $this->doGetProperty($property, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $value New value.
|
||||
* @return $this
|
||||
*/
|
||||
public function setProperty($property, $value)
|
||||
{
|
||||
$this->doSetProperty($property, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be unset.
|
||||
* @return $this
|
||||
*/
|
||||
public function unsetProperty($property)
|
||||
{
|
||||
$this->doUnsetProperty($property);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be defined.
|
||||
* @param mixed $default Default value.
|
||||
* @return $this
|
||||
*/
|
||||
public function defProperty($property, $default)
|
||||
{
|
||||
if (!$this->hasProperty($property)) {
|
||||
$this->setProperty($property, $default);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Serializable interface.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return serialize($this->doSerialize());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $serialized
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
$data = unserialize($serialized);
|
||||
|
||||
if (method_exists($this, 'initObjectProperties')) {
|
||||
$this->initObjectProperties();
|
||||
}
|
||||
$this->doUnserialize($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function doSerialize()
|
||||
{
|
||||
return $this->jsonSerialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $serialized
|
||||
*/
|
||||
protected function doUnserialize(array $serialized)
|
||||
{
|
||||
if (!isset($serialized['key'], $serialized['type'], $serialized['elements']) || $serialized['type'] !== $this->getType()) {
|
||||
throw new \InvalidArgumentException("Cannot unserialize '{$this->getType()}': Bad data");
|
||||
}
|
||||
|
||||
$this->setKey($serialized['key']);
|
||||
$this->setElements($serialized['elements']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements JsonSerializable interface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return ['key' => $this->getKey(), 'type' => $this->getType(), 'elements' => $this->getElements()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
protected function setKey($key)
|
||||
{
|
||||
$this->_key = (string) $key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
abstract protected function doHasProperty($property);
|
||||
abstract protected function &doGetProperty($property, $default = null, $doCreate = false);
|
||||
abstract protected function doSetProperty($property, $value);
|
||||
abstract protected function doUnsetProperty($property);
|
||||
abstract protected function getElements();
|
||||
abstract protected function setElements(array $elements);
|
||||
}
|
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Collection;
|
||||
|
||||
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
|
||||
use Doctrine\Common\Collections\Expr\Comparison;
|
||||
|
||||
class ObjectExpressionVisitor extends ClosureExpressionVisitor
|
||||
{
|
||||
/**
|
||||
* Accesses the field of a given object.
|
||||
*
|
||||
* @param object $object
|
||||
* @param string $field
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getObjectFieldValue($object, $field)
|
||||
{
|
||||
$op = $value = null;
|
||||
|
||||
$pos = strpos($field, '(');
|
||||
if (false !== $pos) {
|
||||
list ($op, $field) = explode('(', $field, 2);
|
||||
$field = rtrim($field, ')');
|
||||
}
|
||||
|
||||
if (isset($object[$field])) {
|
||||
$value = $object[$field];
|
||||
} else {
|
||||
$accessors = array('', 'get', 'is');
|
||||
|
||||
foreach ($accessors as $accessor) {
|
||||
$accessor .= $field;
|
||||
|
||||
if (!method_exists($object, $accessor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $object->{$accessor}();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($op) {
|
||||
$function = 'filter' . ucfirst(strtolower($op));
|
||||
if (method_exists(static::class, $function)) {
|
||||
$value = static::$function($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public static function filterLower($str)
|
||||
{
|
||||
return mb_strtolower($str);
|
||||
}
|
||||
|
||||
public static function filterUpper($str)
|
||||
{
|
||||
return mb_strtoupper($str);
|
||||
}
|
||||
|
||||
public static function filterLength($str)
|
||||
{
|
||||
return mb_strlen($str);
|
||||
}
|
||||
|
||||
public static function filterLtrim($str)
|
||||
{
|
||||
return ltrim($str);
|
||||
}
|
||||
|
||||
public static function filterRtrim($str)
|
||||
{
|
||||
return rtrim($str);
|
||||
}
|
||||
|
||||
public static function filterTrim($str)
|
||||
{
|
||||
return trim($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for sorting arrays of objects based on multiple fields + orientations.
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $orientation
|
||||
* @param \Closure $next
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public static function sortByField($name, $orientation = 1, \Closure $next = null)
|
||||
{
|
||||
if (!$next) {
|
||||
$next = function() {
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
return function ($a, $b) use ($name, $next, $orientation) {
|
||||
$aValue = static::getObjectFieldValue($a, $name);
|
||||
$bValue = static::getObjectFieldValue($b, $name);
|
||||
|
||||
if ($aValue === $bValue) {
|
||||
return $next($a, $b);
|
||||
}
|
||||
|
||||
return (($aValue > $bValue) ? 1 : -1) * $orientation;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function walkComparison(Comparison $comparison)
|
||||
{
|
||||
$field = $comparison->getField();
|
||||
$value = $comparison->getValue()->getValue(); // shortcut for walkValue()
|
||||
|
||||
switch ($comparison->getOperator()) {
|
||||
case Comparison::EQ:
|
||||
return function ($object) use ($field, $value) {
|
||||
return static::getObjectFieldValue($object, $field) === $value;
|
||||
};
|
||||
|
||||
case Comparison::NEQ:
|
||||
return function ($object) use ($field, $value) {
|
||||
return static::getObjectFieldValue($object, $field) !== $value;
|
||||
};
|
||||
|
||||
case Comparison::LT:
|
||||
return function ($object) use ($field, $value) {
|
||||
return static::getObjectFieldValue($object, $field) < $value;
|
||||
};
|
||||
|
||||
case Comparison::LTE:
|
||||
return function ($object) use ($field, $value) {
|
||||
return static::getObjectFieldValue($object, $field) <= $value;
|
||||
};
|
||||
|
||||
case Comparison::GT:
|
||||
return function ($object) use ($field, $value) {
|
||||
return static::getObjectFieldValue($object, $field) > $value;
|
||||
};
|
||||
|
||||
case Comparison::GTE:
|
||||
return function ($object) use ($field, $value) {
|
||||
return static::getObjectFieldValue($object, $field) >= $value;
|
||||
};
|
||||
|
||||
case Comparison::IN:
|
||||
return function ($object) use ($field, $value) {
|
||||
return \in_array(static::getObjectFieldValue($object, $field), $value, true);
|
||||
};
|
||||
|
||||
case Comparison::NIN:
|
||||
return function ($object) use ($field, $value) {
|
||||
return !\in_array(static::getObjectFieldValue($object, $field), $value, true);
|
||||
};
|
||||
|
||||
case Comparison::CONTAINS:
|
||||
return function ($object) use ($field, $value) {
|
||||
return false !== strpos(static::getObjectFieldValue($object, $field), $value);
|
||||
};
|
||||
|
||||
case Comparison::MEMBER_OF:
|
||||
return function ($object) use ($field, $value) {
|
||||
$fieldValues = static::getObjectFieldValue($object, $field);
|
||||
if (!is_array($fieldValues)) {
|
||||
$fieldValues = iterator_to_array($fieldValues);
|
||||
}
|
||||
return \in_array($value, $fieldValues, true);
|
||||
};
|
||||
|
||||
case Comparison::STARTS_WITH:
|
||||
return function ($object) use ($field, $value) {
|
||||
return 0 === strpos(static::getObjectFieldValue($object, $field), $value);
|
||||
};
|
||||
|
||||
case Comparison::ENDS_WITH:
|
||||
return function ($object) use ($field, $value) {
|
||||
return $value === substr(static::getObjectFieldValue($object, $field), -strlen($value));
|
||||
};
|
||||
|
||||
|
||||
default:
|
||||
throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Interfaces;
|
||||
|
||||
/**
|
||||
* Object Interface
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
interface NestedObjectInterface extends ObjectInterface
|
||||
{
|
||||
/**
|
||||
* @param string $property Object property name.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return bool True if property has been defined (can be null).
|
||||
*/
|
||||
public function hasNestedProperty($property, $separator = null);
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if property has not been set.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return mixed Property value.
|
||||
*/
|
||||
public function getNestedProperty($property, $default = null, $separator = null);
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $value New value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setNestedProperty($property, $value, $separator = null);
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be defined.
|
||||
* @param string $default Default value.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function defNestedProperty($property, $default, $separator = null);
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be unset.
|
||||
* @param string $separator Separator, defaults to '.'
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function unsetNestedProperty($property, $separator = null);
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Interfaces;
|
||||
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Grav\Framework\Collection\CollectionInterface;
|
||||
|
||||
/**
|
||||
* ObjectCollection Interface
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
interface ObjectCollectionInterface extends CollectionInterface, Selectable, ObjectInterface
|
||||
{
|
||||
/**
|
||||
* Create a copy from this collection by cloning all objects in the collection.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy();
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
public function setKey($key);
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getObjectKeys();
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if not set.
|
||||
* @return array Property value.
|
||||
*/
|
||||
public function getProperty($property, $default = null);
|
||||
|
||||
/**
|
||||
* @param string $name Method name.
|
||||
* @param array $arguments List of arguments passed to the function.
|
||||
* @return array Return values.
|
||||
*/
|
||||
public function call($name, array $arguments);
|
||||
|
||||
/**
|
||||
* Group items in the collection by a field and return them as associated array.
|
||||
*
|
||||
* @param string $property
|
||||
* @return array
|
||||
*/
|
||||
public function group($property);
|
||||
|
||||
/**
|
||||
* Group items in the collection by a field and return them as associated array of collections.
|
||||
*
|
||||
* @param string $property
|
||||
* @return static[]
|
||||
*/
|
||||
public function collectionGroup($property);
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Interfaces;
|
||||
|
||||
/**
|
||||
* Object Interface
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
interface ObjectInterface extends \Serializable, \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getKey();
|
||||
|
||||
/**
|
||||
* @param string $property Object property name.
|
||||
* @return bool True if property has been defined (can be null).
|
||||
*/
|
||||
public function hasProperty($property);
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if property has not been set.
|
||||
* @return mixed Property value.
|
||||
*/
|
||||
public function getProperty($property, $default = null);
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $value New value.
|
||||
* @return $this
|
||||
*/
|
||||
public function setProperty($property, $value);
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be defined.
|
||||
* @param mixed $default Default value.
|
||||
* @return $this
|
||||
*/
|
||||
public function defProperty($property, $default);
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be unset.
|
||||
* @return $this
|
||||
*/
|
||||
public function unsetProperty($property);
|
||||
}
|
26
system/src/Grav/Framework/Object/LazyObject.php
Normal file
26
system/src/Grav/Framework/Object/LazyObject.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
use Grav\Framework\Object\Access\NestedArrayAccessTrait;
|
||||
use Grav\Framework\Object\Access\NestedPropertyTrait;
|
||||
use Grav\Framework\Object\Access\OverloadedPropertyTrait;
|
||||
use Grav\Framework\Object\Base\ObjectTrait;
|
||||
use Grav\Framework\Object\Interfaces\NestedObjectInterface;
|
||||
use Grav\Framework\Object\Property\LazyPropertyTrait;
|
||||
|
||||
/**
|
||||
* Lazy Object class.
|
||||
*
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
class LazyObject implements NestedObjectInterface, \ArrayAccess
|
||||
{
|
||||
use ObjectTrait, LazyPropertyTrait, NestedPropertyTrait, OverloadedPropertyTrait, NestedArrayAccessTrait;
|
||||
}
|
83
system/src/Grav/Framework/Object/ObjectCollection.php
Normal file
83
system/src/Grav/Framework/Object/ObjectCollection.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Grav\Framework\Collection\ArrayCollection;
|
||||
use Grav\Framework\Object\Access\NestedPropertyCollectionTrait;
|
||||
use Grav\Framework\Object\Base\ObjectCollectionTrait;
|
||||
use Grav\Framework\Object\Collection\ObjectExpressionVisitor;
|
||||
use Grav\Framework\Object\Interfaces\NestedObjectInterface;
|
||||
use Grav\Framework\Object\Interfaces\ObjectCollectionInterface;
|
||||
|
||||
/**
|
||||
* Object Collection
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
class ObjectCollection extends ArrayCollection implements ObjectCollectionInterface, NestedObjectInterface
|
||||
{
|
||||
use ObjectCollectionTrait, NestedPropertyCollectionTrait {
|
||||
NestedPropertyCollectionTrait::group insteadof ObjectCollectionTrait;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
* @param string $key
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
parent::__construct($this->setElements($elements));
|
||||
|
||||
$this->setKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function matching(Criteria $criteria)
|
||||
{
|
||||
$expr = $criteria->getWhereExpression();
|
||||
$filtered = $this->getElements();
|
||||
|
||||
if ($expr) {
|
||||
$visitor = new ObjectExpressionVisitor();
|
||||
$filter = $visitor->dispatch($expr);
|
||||
$filtered = array_filter($filtered, $filter);
|
||||
}
|
||||
|
||||
if ($orderings = $criteria->getOrderings()) {
|
||||
$next = null;
|
||||
foreach (array_reverse($orderings) as $field => $ordering) {
|
||||
$next = ObjectExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1, $next);
|
||||
}
|
||||
|
||||
uasort($filtered, $next);
|
||||
}
|
||||
|
||||
$offset = $criteria->getFirstResult();
|
||||
$length = $criteria->getMaxResults();
|
||||
|
||||
if ($offset || $length) {
|
||||
$filtered = array_slice($filtered, (int)$offset, $length);
|
||||
}
|
||||
|
||||
return $this->createFrom($filtered);
|
||||
}
|
||||
|
||||
protected function getElements()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
protected function setElements(array $elements)
|
||||
{
|
||||
return $elements;
|
||||
}
|
||||
}
|
109
system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php
Normal file
109
system/src/Grav/Framework/Object/Property/ArrayPropertyTrait.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Property;
|
||||
|
||||
/**
|
||||
* Array Property Trait
|
||||
*
|
||||
* Stores all object properties into an array.
|
||||
*
|
||||
* @package Grav\Framework\Object\Property
|
||||
*/
|
||||
trait ArrayPropertyTrait
|
||||
{
|
||||
/**
|
||||
* Properties of the object.
|
||||
* @var array
|
||||
*/
|
||||
private $_elements;
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
* @param string $key
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$this->setElements($elements);
|
||||
$this->setKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property name.
|
||||
* @return bool True if property has been defined (can be null).
|
||||
*/
|
||||
protected function doHasProperty($property)
|
||||
{
|
||||
return array_key_exists($property, $this->_elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if property has not been set.
|
||||
* @param bool $doCreate Set true to create variable.
|
||||
* @return mixed Property value.
|
||||
*/
|
||||
protected function &doGetProperty($property, $default = null, $doCreate = false)
|
||||
{
|
||||
if (!array_key_exists($property, $this->_elements)) {
|
||||
if ($doCreate) {
|
||||
$this->_elements[$property] = null;
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_elements[$property];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param mixed $value New value.
|
||||
*/
|
||||
protected function doSetProperty($property, $value)
|
||||
{
|
||||
$this->_elements[$property] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be unset.
|
||||
*/
|
||||
protected function doUnsetProperty($property)
|
||||
{
|
||||
unset($this->_elements[$property]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
* @param mixed|null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function getElement($property, $default = null)
|
||||
{
|
||||
return array_key_exists($property, $this->_elements) ? $this->_elements[$property] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getElements()
|
||||
{
|
||||
return $this->_elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
*/
|
||||
protected function setElements(array $elements)
|
||||
{
|
||||
$this->_elements = $elements;
|
||||
}
|
||||
|
||||
abstract protected function setKey($key);
|
||||
}
|
117
system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php
Normal file
117
system/src/Grav/Framework/Object/Property/LazyPropertyTrait.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Property;
|
||||
|
||||
/**
|
||||
* Lazy Mixed Property Trait
|
||||
*
|
||||
* Stores defined object properties as class member variables and the rest into an array. Object properties are lazy
|
||||
* loaded from the array.
|
||||
*
|
||||
* You may define following methods for the member variables:
|
||||
* - `$this->offsetLoad($offset, $value)` called first time object property gets accessed
|
||||
* - `$this->offsetPrepare($offset, $value)` called on every object property set
|
||||
* - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed
|
||||
*
|
||||
* @package Grav\Framework\Object\Property
|
||||
*/
|
||||
trait LazyPropertyTrait
|
||||
{
|
||||
use ArrayPropertyTrait, ObjectPropertyTrait {
|
||||
ObjectPropertyTrait::__construct insteadof ArrayPropertyTrait;
|
||||
ArrayPropertyTrait::doHasProperty as hasArrayProperty;
|
||||
ArrayPropertyTrait::doGetProperty as getArrayProperty;
|
||||
ArrayPropertyTrait::doSetProperty as setArrayProperty;
|
||||
ArrayPropertyTrait::doUnsetProperty as unsetArrayProperty;
|
||||
ArrayPropertyTrait::getElement as getArrayElement;
|
||||
ArrayPropertyTrait::getElements as getArrayElements;
|
||||
ArrayPropertyTrait::setElements insteadof ObjectPropertyTrait;
|
||||
ObjectPropertyTrait::doHasProperty as hasObjectProperty;
|
||||
ObjectPropertyTrait::doGetProperty as getObjectProperty;
|
||||
ObjectPropertyTrait::doSetProperty as setObjectProperty;
|
||||
ObjectPropertyTrait::doUnsetProperty as unsetObjectProperty;
|
||||
ObjectPropertyTrait::getElement as getObjectElement;
|
||||
ObjectPropertyTrait::getElements as getObjectElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property name.
|
||||
* @return bool True if property has been defined (can be null).
|
||||
*/
|
||||
protected function doHasProperty($property)
|
||||
{
|
||||
return $this->hasArrayProperty($property) || $this->hasObjectProperty($property);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if property has not been set.
|
||||
* @return mixed Property value.
|
||||
*/
|
||||
protected function &doGetProperty($property, $default = null, $doCreate = false)
|
||||
{
|
||||
if ($this->hasObjectProperty($property)) {
|
||||
return $this->getObjectProperty($property, $default, function ($default = null) use ($property) {
|
||||
return $this->getArrayProperty($property, $default);
|
||||
});
|
||||
}
|
||||
|
||||
return $this->getArrayProperty($property, $default, $doCreate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param mixed $value New value.
|
||||
* @return $this
|
||||
*/
|
||||
protected function doSetProperty($property, $value)
|
||||
{
|
||||
if ($this->hasObjectProperty($property)) {
|
||||
$this->setObjectProperty($property, $value);
|
||||
} else {
|
||||
$this->setArrayProperty($property, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be unset.
|
||||
* @return $this
|
||||
*/
|
||||
protected function doUnsetProperty($property)
|
||||
{
|
||||
$this->hasObjectProperty($property) ?
|
||||
$this->unsetObjectProperty($property) : $this->unsetArrayProperty($property);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
* @param mixed|null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function getElement($property, $default = null)
|
||||
{
|
||||
if ($this->isPropertyLoaded($property)) {
|
||||
return $this->getObjectElement($property, $default);
|
||||
}
|
||||
|
||||
return $this->getArrayElement($property, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getElements()
|
||||
{
|
||||
return $this->getObjectElements() + $this->getArrayElements();
|
||||
}
|
||||
}
|
122
system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php
Normal file
122
system/src/Grav/Framework/Object/Property/MixedPropertyTrait.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Property;
|
||||
|
||||
/**
|
||||
* Mixed Property Trait
|
||||
*
|
||||
* Stores defined object properties as class member variables and the rest into an array.
|
||||
*
|
||||
* You may define following methods for member variables:
|
||||
* - `$this->offsetLoad($offset, $value)` called first time object property gets accessed
|
||||
* - `$this->offsetPrepare($offset, $value)` called on every object property set
|
||||
* - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed
|
||||
|
||||
*
|
||||
* @package Grav\Framework\Object\Property
|
||||
*/
|
||||
trait MixedPropertyTrait
|
||||
{
|
||||
use ArrayPropertyTrait, ObjectPropertyTrait {
|
||||
ObjectPropertyTrait::__construct insteadof ArrayPropertyTrait;
|
||||
ArrayPropertyTrait::doHasProperty as hasArrayProperty;
|
||||
ArrayPropertyTrait::doGetProperty as getArrayProperty;
|
||||
ArrayPropertyTrait::doSetProperty as setArrayProperty;
|
||||
ArrayPropertyTrait::doUnsetProperty as unsetArrayProperty;
|
||||
ArrayPropertyTrait::getElement as getArrayElement;
|
||||
ArrayPropertyTrait::getElements as getArrayElements;
|
||||
ArrayPropertyTrait::setElements as setArrayElements;
|
||||
ObjectPropertyTrait::doHasProperty as hasObjectProperty;
|
||||
ObjectPropertyTrait::doGetProperty as getObjectProperty;
|
||||
ObjectPropertyTrait::doSetProperty as setObjectProperty;
|
||||
ObjectPropertyTrait::doUnsetProperty as unsetObjectProperty;
|
||||
ObjectPropertyTrait::getElement as getObjectElement;
|
||||
ObjectPropertyTrait::getElements as getObjectElements;
|
||||
ObjectPropertyTrait::setElements as setObjectElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property name.
|
||||
* @return bool True if property has been defined (can be null).
|
||||
*/
|
||||
protected function doHasProperty($property)
|
||||
{
|
||||
return $this->hasArrayProperty($property) || $this->hasObjectProperty($property);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if property has not been set.
|
||||
* @return mixed Property value.
|
||||
*/
|
||||
protected function &doGetProperty($property, $default = null, $doCreate = false)
|
||||
{
|
||||
if ($this->hasObjectProperty($property)) {
|
||||
return $this->getObjectProperty($property);
|
||||
}
|
||||
|
||||
return $this->getArrayProperty($property, $default, $doCreate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param mixed $value New value.
|
||||
* @return $this
|
||||
*/
|
||||
protected function doSetProperty($property, $value)
|
||||
{
|
||||
$this->hasObjectProperty($property)
|
||||
? $this->setObjectProperty($property, $value) : $this->setArrayProperty($property, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be unset.
|
||||
* @return $this
|
||||
*/
|
||||
protected function doUnsetProperty($property)
|
||||
{
|
||||
$this->hasObjectProperty($property) ?
|
||||
$this->unsetObjectProperty($property) : $this->unsetArrayProperty($property);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
* @param mixed|null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function getElement($property, $default = null)
|
||||
{
|
||||
if ($this->hasObjectProperty($property)) {
|
||||
return $this->getObjectElement($property, $default);
|
||||
}
|
||||
|
||||
return $this->getArrayElement($property, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getElements()
|
||||
{
|
||||
return $this->getObjectElements() + $this->getArrayElements();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
*/
|
||||
protected function setElements(array $elements)
|
||||
{
|
||||
$this->setObjectElements(array_intersect_key($elements, $this->_definedProperties));
|
||||
$this->setArrayElements(array_diff_key($elements, $this->_definedProperties));
|
||||
}
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Property;
|
||||
|
||||
/**
|
||||
* Object Property Trait
|
||||
*
|
||||
* Stores all properties as class member variables or object properties. All properties need to be defined as protected
|
||||
* properties. Undefined properties will throw an error.
|
||||
*
|
||||
* Additionally you may define following methods:
|
||||
* - `$this->offsetLoad($offset, $value)` called first time object property gets accessed
|
||||
* - `$this->offsetPrepare($offset, $value)` called on every object property set
|
||||
* - `$this->offsetSerialize($offset, $value)` called when the raw or serialized object property value is needed
|
||||
*
|
||||
* @package Grav\Framework\Object\Property
|
||||
*/
|
||||
trait ObjectPropertyTrait
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $_definedProperties;
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
* @param string $key
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $elements = [], $key = null)
|
||||
{
|
||||
$this->initObjectProperties();
|
||||
$this->setElements($elements);
|
||||
$this->setKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property name.
|
||||
* @return bool True if property has been loaded.
|
||||
*/
|
||||
protected function isPropertyLoaded($property)
|
||||
{
|
||||
return !empty($this->_definedProperties[$property]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function offsetLoad($offset, $value)
|
||||
{
|
||||
$methodName = "offsetLoad_{$offset}";
|
||||
|
||||
return method_exists($this, $methodName)? $this->{$methodName}($value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function offsetPrepare($offset, $value)
|
||||
{
|
||||
$methodName = "offsetPrepare_{$offset}";
|
||||
|
||||
return method_exists($this, $methodName) ? $this->{$methodName}($value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function offsetSerialize($offset, $value)
|
||||
{
|
||||
$methodName = "offsetSerialize_{$offset}";
|
||||
|
||||
return method_exists($this, $methodName) ? $this->{$methodName}($value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property name.
|
||||
* @return bool True if property has been defined (can be null).
|
||||
*/
|
||||
protected function doHasProperty($property)
|
||||
{
|
||||
return array_key_exists($property, $this->_definedProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @param mixed $default Default value if property has not been set.
|
||||
* @param callable|bool $doCreate Set true to create variable.
|
||||
* @return mixed Property value.
|
||||
*/
|
||||
protected function &doGetProperty($property, $default = null, $doCreate = false)
|
||||
{
|
||||
if (!array_key_exists($property, $this->_definedProperties)) {
|
||||
throw new \InvalidArgumentException("Property '{$property}' does not exist in the object!");
|
||||
}
|
||||
|
||||
if (empty($this->_definedProperties[$property])) {
|
||||
if ($doCreate === true) {
|
||||
$this->_definedProperties[$property] = true;
|
||||
$this->{$property} = null;
|
||||
} elseif (is_callable($doCreate)) {
|
||||
$this->_definedProperties[$property] = true;
|
||||
$this->{$property} = $this->offsetLoad($property, $doCreate());
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->{$property};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param mixed $value New value.
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function doSetProperty($property, $value)
|
||||
{
|
||||
if (!array_key_exists($property, $this->_definedProperties)) {
|
||||
throw new \InvalidArgumentException("Property '{$property}' does not exist in the object!");
|
||||
}
|
||||
|
||||
$this->_definedProperties[$property] = true;
|
||||
$this->{$property} = $this->offsetPrepare($property, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be unset.
|
||||
*/
|
||||
protected function doUnsetProperty($property)
|
||||
{
|
||||
if (!array_key_exists($property, $this->_definedProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_definedProperties[$property] = false;
|
||||
unset($this->{$property});
|
||||
}
|
||||
|
||||
protected function initObjectProperties()
|
||||
{
|
||||
$this->_definedProperties = [];
|
||||
foreach (get_object_vars($this) as $property => $value) {
|
||||
if ($property[0] !== '_') {
|
||||
$this->_definedProperties[$property] = ($value !== null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
* @param mixed|null $default
|
||||
* @return mixed|null
|
||||
*/
|
||||
protected function getElement($property, $default = null)
|
||||
{
|
||||
if (empty($this->_definedProperties[$property])) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $this->offsetSerialize($property, $this->{$property});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getElements()
|
||||
{
|
||||
$properties = array_intersect_key(get_object_vars($this), array_filter($this->_definedProperties));
|
||||
|
||||
$elements = [];
|
||||
foreach ($properties as $offset => $value) {
|
||||
$elements[$offset] = $this->offsetSerialize($offset, $value);
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $elements
|
||||
*/
|
||||
protected function setElements(array $elements)
|
||||
{
|
||||
foreach ($elements as $property => $value) {
|
||||
$this->setProperty($property, $value);
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function setProperty($property, $value);
|
||||
abstract protected function setKey($key);
|
||||
}
|
26
system/src/Grav/Framework/Object/PropertyObject.php
Normal file
26
system/src/Grav/Framework/Object/PropertyObject.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
use Grav\Framework\Object\Access\NestedArrayAccessTrait;
|
||||
use Grav\Framework\Object\Access\NestedPropertyTrait;
|
||||
use Grav\Framework\Object\Access\OverloadedPropertyTrait;
|
||||
use Grav\Framework\Object\Base\ObjectTrait;
|
||||
use Grav\Framework\Object\Interfaces\NestedObjectInterface;
|
||||
use Grav\Framework\Object\Property\ObjectPropertyTrait;
|
||||
|
||||
/**
|
||||
* Property Object class.
|
||||
*
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
class PropertyObject implements NestedObjectInterface, \ArrayAccess
|
||||
{
|
||||
use ObjectTrait, ObjectPropertyTrait, NestedPropertyTrait, OverloadedPropertyTrait, NestedArrayAccessTrait;
|
||||
}
|
406
system/src/Grav/Framework/Psr7/AbstractUri.php
Normal file
406
system/src/Grav/Framework/Psr7/AbstractUri.php
Normal file
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Psr7
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Psr7;
|
||||
|
||||
use Grav\Framework\Uri\UriPartsFilter;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Bare minimum PSR7 implementation.
|
||||
*
|
||||
* @package Grav\Framework\Uri\Psr7
|
||||
*/
|
||||
abstract class AbstractUri implements UriInterface
|
||||
{
|
||||
protected static $defaultPorts = [
|
||||
'http' => 80,
|
||||
'https' => 443
|
||||
];
|
||||
|
||||
/** @var string Uri scheme. */
|
||||
private $scheme = '';
|
||||
|
||||
/** @var string Uri user. */
|
||||
private $user = '';
|
||||
|
||||
/** @var string Uri password. */
|
||||
private $password = '';
|
||||
|
||||
/** @var string Uri host. */
|
||||
private $host = '';
|
||||
|
||||
/** @var int|null Uri port. */
|
||||
private $port;
|
||||
|
||||
/** @var string Uri path. */
|
||||
private $path = '';
|
||||
|
||||
/** @var string Uri query string (without ?). */
|
||||
private $query = '';
|
||||
|
||||
/** @var string Uri fragment (without #). */
|
||||
private $fragment = '';
|
||||
|
||||
/**
|
||||
* Please define constructor which calls $this->init().
|
||||
*/
|
||||
abstract public function __construct();
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getScheme()
|
||||
{
|
||||
return $this->scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getAuthority()
|
||||
{
|
||||
$authority = $this->host;
|
||||
|
||||
$userInfo = $this->getUserInfo();
|
||||
if ($userInfo !== '') {
|
||||
$authority = $userInfo . '@' . $authority;
|
||||
}
|
||||
|
||||
if ($this->port !== null) {
|
||||
$authority .= ':' . $this->port;
|
||||
}
|
||||
|
||||
return $authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getUserInfo()
|
||||
{
|
||||
$userInfo = $this->user;
|
||||
|
||||
if ($this->password !== '') {
|
||||
$userInfo .= ':' . $this->password;
|
||||
}
|
||||
|
||||
return $userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getPort()
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getFragment()
|
||||
{
|
||||
return $this->fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function withScheme($scheme)
|
||||
{
|
||||
$scheme = UriPartsFilter::filterScheme($scheme);
|
||||
|
||||
if ($this->scheme === $scheme) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->scheme = $scheme;
|
||||
$new->unsetDefaultPort();
|
||||
$new->validate();
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function withUserInfo($user, $password = '')
|
||||
{
|
||||
$user = UriPartsFilter::filterUserInfo($user);
|
||||
$password = UriPartsFilter::filterUserInfo($password);
|
||||
|
||||
if ($this->user === $user && $this->password === $password) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->user = $user;
|
||||
$new->password = $user !== '' ? $password : '';
|
||||
$new->validate();
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function withHost($host)
|
||||
{
|
||||
$host = UriPartsFilter::filterHost($host);
|
||||
|
||||
if ($this->host === $host) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->host = $host;
|
||||
$new->validate();
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function withPort($port)
|
||||
{
|
||||
$port = UriPartsFilter::filterPort($port);
|
||||
|
||||
if ($this->port === $port) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->port = $port;
|
||||
$new->unsetDefaultPort();
|
||||
$new->validate();
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function withPath($path)
|
||||
{
|
||||
$path = UriPartsFilter::filterPath($path);
|
||||
|
||||
if ($this->path === $path) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->path = $path;
|
||||
$new->validate();
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function withQuery($query)
|
||||
{
|
||||
$query = UriPartsFilter::filterQueryOrFragment($query);
|
||||
|
||||
if ($this->query === $query) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->query = $query;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function withFragment($fragment)
|
||||
{
|
||||
$fragment = UriPartsFilter::filterQueryOrFragment($fragment);
|
||||
|
||||
if ($this->fragment === $fragment) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
$new->fragment = $fragment;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getParts()
|
||||
{
|
||||
return [
|
||||
'scheme' => $this->scheme,
|
||||
'host' => $this->host,
|
||||
'port' => $this->port,
|
||||
'user' => $this->user,
|
||||
'pass' => $this->password,
|
||||
'path' => $this->path,
|
||||
'query' => $this->query,
|
||||
'fragment' => $this->fragment
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully qualified base URL ( like http://getgrav.org ).
|
||||
*
|
||||
* Note that this method never includes a trailing /
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getBaseUrl()
|
||||
{
|
||||
$uri = '';
|
||||
|
||||
$scheme = $this->getScheme();
|
||||
if ($scheme !== '') {
|
||||
$uri .= $scheme . ':';
|
||||
}
|
||||
|
||||
$authority = $this->getAuthority();
|
||||
if ($authority !== '' || $scheme === 'file') {
|
||||
$uri .= '//' . $authority;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getUrl()
|
||||
{
|
||||
$uri = $this->getBaseUrl() . $this->getPath();
|
||||
|
||||
$query = $this->getQuery();
|
||||
if ($query !== '') {
|
||||
$uri .= '?' . $query;
|
||||
}
|
||||
|
||||
$fragment = $this->getFragment();
|
||||
if ($fragment !== '') {
|
||||
$uri .= '#' . $fragment;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getPassword()
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parts
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function initParts(array $parts)
|
||||
{
|
||||
$this->scheme = isset($parts['scheme']) ? UriPartsFilter::filterScheme($parts['scheme']) : '';
|
||||
$this->user = isset($parts['user']) ? UriPartsFilter::filterUserInfo($parts['user']) : '';
|
||||
$this->password = isset($parts['pass']) ? UriPartsFilter::filterUserInfo($parts['pass']) : '';
|
||||
$this->host = isset($parts['host']) ? UriPartsFilter::filterHost($parts['host']) : '';
|
||||
$this->port = isset($parts['port']) ? UriPartsFilter::filterPort((int)$parts['port']) : null;
|
||||
$this->path = isset($parts['path']) ? UriPartsFilter::filterPath($parts['path']) : '';
|
||||
$this->query = isset($parts['query']) ? UriPartsFilter::filterQueryOrFragment($parts['query']) : '';
|
||||
$this->fragment = isset($parts['fragment']) ? UriPartsFilter::filterQueryOrFragment($parts['fragment']) : '';
|
||||
|
||||
$this->unsetDefaultPort();
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function validate()
|
||||
{
|
||||
if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
|
||||
throw new \InvalidArgumentException('Uri with a scheme must have a host');
|
||||
}
|
||||
|
||||
if ($this->getAuthority() === '') {
|
||||
if (0 === strpos($this->path, '//')) {
|
||||
throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes \'//\'');
|
||||
}
|
||||
if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
|
||||
throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
|
||||
}
|
||||
} elseif (isset($this->path[0]) && $this->path[0] !== '/') {
|
||||
throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash \'/\' or be empty');
|
||||
}
|
||||
}
|
||||
|
||||
protected function isDefaultPort()
|
||||
{
|
||||
$scheme = $this->scheme;
|
||||
$port = $this->port;
|
||||
|
||||
return $this->port === null
|
||||
|| (isset(static::$defaultPorts[$scheme]) && $port === static::$defaultPorts[$scheme]);
|
||||
}
|
||||
|
||||
private function unsetDefaultPort()
|
||||
{
|
||||
if ($this->isDefaultPort()) {
|
||||
$this->port = null;
|
||||
}
|
||||
}
|
||||
}
|
300
system/src/Grav/Framework/Route/Route.php
Normal file
300
system/src/Grav/Framework/Route/Route.php
Normal file
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Route
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Route;
|
||||
|
||||
use Grav\Framework\Uri\UriFactory;
|
||||
|
||||
/**
|
||||
* Implements Grav Route.
|
||||
*
|
||||
* @package Grav\Framework\Route
|
||||
*/
|
||||
class Route
|
||||
{
|
||||
/** @var string */
|
||||
private $root = '';
|
||||
|
||||
/** @var string */
|
||||
private $language = '';
|
||||
|
||||
/** @var string */
|
||||
private $route = '';
|
||||
|
||||
/** @var array */
|
||||
private $gravParams = [];
|
||||
|
||||
/** @var array */
|
||||
private $queryParams = [];
|
||||
|
||||
/**
|
||||
* You can use `RouteFactory` functions to create new `Route` objects.
|
||||
*
|
||||
* @param array $parts
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $parts = [])
|
||||
{
|
||||
$this->initParts($parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getParts()
|
||||
{
|
||||
return [
|
||||
'path' => $this->getUriPath(),
|
||||
'query' => $this->getUriQuery(),
|
||||
'grav' => [
|
||||
'root' => $this->root,
|
||||
'language' => $this->language,
|
||||
'route' => $this->route,
|
||||
'grav_params' => $this->gravParams,
|
||||
'query_params' => $this->queryParams,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRootPrefix()
|
||||
{
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLanguagePrefix()
|
||||
{
|
||||
return $this->language !== '' ? '/' . $this->language : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int|null $length
|
||||
* @return string
|
||||
*/
|
||||
public function getRoute($offset = 0, $length = null)
|
||||
{
|
||||
if ($offset !== 0 || $length !== null) {
|
||||
return ($offset === 0 ? '/' : '') . implode('/', $this->getRouteParts($offset, $length));
|
||||
}
|
||||
|
||||
return '/' . $this->route;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int|null $length
|
||||
* @return array
|
||||
*/
|
||||
public function getRouteParts($offset = 0, $length = null)
|
||||
{
|
||||
$parts = explode('/', $this->route);
|
||||
|
||||
if ($offset !== 0 || $length !== null) {
|
||||
$parts = array_slice($parts, $offset, $length);
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of both query and Grav parameters.
|
||||
*
|
||||
* If a parameter exists in both, prefer Grav parameter.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParams()
|
||||
{
|
||||
return $this->gravParams + $this->queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getGravParams()
|
||||
{
|
||||
return $this->gravParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getQueryParams()
|
||||
{
|
||||
return $this->queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value of the parameter, looking into both Grav parameters and query parameters.
|
||||
*
|
||||
* If the parameter exists in both, return Grav parameter.
|
||||
*
|
||||
* @param string $param
|
||||
* @return string|null
|
||||
*/
|
||||
public function getParam($param)
|
||||
{
|
||||
$value = $this->getGravParam($param);
|
||||
if ($value === null) {
|
||||
$value = $this->getQueryParam($param);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $param
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGravParam($param)
|
||||
{
|
||||
return isset($this->gravParams[$param]) ? $this->gravParams[$param] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $param
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQueryParam($param)
|
||||
{
|
||||
return isset($this->queryParams[$param]) ? $this->queryParams[$param] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $param
|
||||
* @param mixed $value
|
||||
* @return Route
|
||||
*/
|
||||
public function withGravParam($param, $value)
|
||||
{
|
||||
return $this->withParam('gravParams', $param, null !== $value ? (string)$value : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $param
|
||||
* @param mixed $value
|
||||
* @return Route
|
||||
*/
|
||||
public function withQueryParam($param, $value)
|
||||
{
|
||||
return $this->withParam('queryParams', $param, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Grav\Framework\Uri\Uri
|
||||
*/
|
||||
public function getUri()
|
||||
{
|
||||
return UriFactory::createFromParts($this->getParts());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$url = $this->getUriPath();
|
||||
|
||||
if ($this->queryParams) {
|
||||
$url .= '?' . $this->getUriQuery();
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $param
|
||||
* @param mixed $value
|
||||
* @return static
|
||||
*/
|
||||
protected function withParam($type, $param, $value)
|
||||
{
|
||||
$oldValue = isset($this->{$type}[$param]) ? $this->{$type}[$param] : null;
|
||||
|
||||
if ($oldValue === $value) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
if ($value === null) {
|
||||
unset($new->{$type}[$param]);
|
||||
} else {
|
||||
$new->{$type}[$param] = $value;
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getUriPath()
|
||||
{
|
||||
$parts = [$this->root];
|
||||
|
||||
if ($this->language !== '') {
|
||||
$parts[] = $this->language;
|
||||
}
|
||||
|
||||
if ($this->route !== '') {
|
||||
$parts[] = $this->route;
|
||||
}
|
||||
|
||||
if ($this->gravParams) {
|
||||
$parts[] = RouteFactory::buildParams($this->gravParams);
|
||||
}
|
||||
|
||||
return implode('/', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getUriQuery()
|
||||
{
|
||||
return UriFactory::buildQuery($this->queryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parts
|
||||
*/
|
||||
protected function initParts(array $parts)
|
||||
{
|
||||
if (isset($parts['grav'])) {
|
||||
$gravParts = $parts['grav'];
|
||||
$this->root = $gravParts['root'];
|
||||
$this->language = $gravParts['language'];
|
||||
$this->route = $gravParts['route'];
|
||||
$this->gravParams = $gravParts['params'];
|
||||
$this->queryParams = $parts['query_params'];
|
||||
|
||||
} else {
|
||||
$this->root = RouteFactory::getRoot();
|
||||
$this->language = RouteFactory::getLanguage();
|
||||
|
||||
$path = isset($parts['path']) ? $parts['path'] : '/';
|
||||
if (isset($parts['params'])) {
|
||||
$this->route = trim(rawurldecode($path), '/');
|
||||
$this->gravParams = $parts['params'];
|
||||
} else {
|
||||
$this->route = trim(RouteFactory::stripParams($path, true), '/');
|
||||
$this->gravParams = RouteFactory::getParams($path);
|
||||
}
|
||||
if (isset($parts['query'])) {
|
||||
$this->queryParams = UriFactory::parseQuery($parts['query']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
148
system/src/Grav/Framework/Route/RouteFactory.php
Normal file
148
system/src/Grav/Framework/Route/RouteFactory.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Route
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Route;
|
||||
|
||||
/**
|
||||
* Class RouteFactory
|
||||
* @package Grav\Framework\Route
|
||||
*/
|
||||
class RouteFactory
|
||||
{
|
||||
/** @var string */
|
||||
private static $root = '';
|
||||
|
||||
/** @var string */
|
||||
private static $language = '';
|
||||
|
||||
/** @var string */
|
||||
private static $delimiter = ':';
|
||||
|
||||
public static function createFromParts($parts)
|
||||
{
|
||||
return new Route($parts);
|
||||
}
|
||||
|
||||
public static function createFromString($path)
|
||||
{
|
||||
$path = ltrim($path, '/');
|
||||
$parts = [
|
||||
'path' => $path,
|
||||
'query' => '',
|
||||
'query_params' => [],
|
||||
'grav' => [
|
||||
'root' => self::$root,
|
||||
'language' => self::$language,
|
||||
'route' => $path,
|
||||
'params' => ''
|
||||
],
|
||||
];
|
||||
return new Route($parts);
|
||||
}
|
||||
|
||||
public static function getRoot()
|
||||
{
|
||||
return self::$root;
|
||||
}
|
||||
|
||||
public static function setRoot($root)
|
||||
{
|
||||
self::$root = rtrim($root, '/');
|
||||
}
|
||||
|
||||
public static function getLanguage()
|
||||
{
|
||||
return self::$language;
|
||||
}
|
||||
|
||||
public static function setLanguage($language)
|
||||
{
|
||||
self::$language = trim($language, '/');
|
||||
}
|
||||
|
||||
public static function getParamValueDelimiter()
|
||||
{
|
||||
return self::$delimiter;
|
||||
}
|
||||
|
||||
public static function setParamValueDelimiter($delimiter)
|
||||
{
|
||||
self::$delimiter = $delimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public static function buildParams(array $params)
|
||||
{
|
||||
if (!$params) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$delimiter = self::$delimiter;
|
||||
|
||||
$output = [];
|
||||
foreach ($params as $key => $value) {
|
||||
$output[] = "{$key}{$delimiter}{$value}";
|
||||
}
|
||||
|
||||
return implode('/', $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param bool $decode
|
||||
* @return string
|
||||
*/
|
||||
public static function stripParams($path, $decode = false)
|
||||
{
|
||||
$pos = strpos($path, self::$delimiter);
|
||||
|
||||
if ($pos === false) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
$path = dirname(substr($path, 0, $pos));
|
||||
if ($path === '.') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $decode ? rawurldecode($path) : $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return array
|
||||
*/
|
||||
public static function getParams($path)
|
||||
{
|
||||
$params = ltrim(substr($path, strlen(static::stripParams($path))), '/');
|
||||
|
||||
return $params !== '' ? static::parseParams($params) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return array
|
||||
*/
|
||||
public static function parseParams($str)
|
||||
{
|
||||
$delimiter = self::$delimiter;
|
||||
|
||||
$params = explode('/', $str);
|
||||
foreach ($params as &$param) {
|
||||
$parts = explode($delimiter, $param, 2);
|
||||
if (isset($parts[1])) {
|
||||
$param[rawurldecode($parts[0])] = rawurldecode($parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
345
system/src/Grav/Framework/Session/Session.php
Normal file
345
system/src/Grav/Framework/Session/Session.php
Normal file
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Session
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Session;
|
||||
|
||||
/**
|
||||
* Class Session
|
||||
* @package Grav\Framework\Session
|
||||
*/
|
||||
class Session implements SessionInterface
|
||||
{
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $started = false;
|
||||
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
throw new \RuntimeException("Session hasn't been initialized.", 500);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
// Session is a singleton.
|
||||
if (\PHP_SAPI === 'cli') {
|
||||
self::$instance = $this;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== self::$instance) {
|
||||
throw new \RuntimeException('Session has already been initialized.', 500);
|
||||
}
|
||||
|
||||
// Destroy any existing sessions started with session.auto_start
|
||||
if ($this->isSessionStarted()) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
// Set default options.
|
||||
$options += array(
|
||||
'cache_limiter' => 'nocache',
|
||||
'use_trans_sid' => 0,
|
||||
'use_cookies' => 1,
|
||||
'lazy_write' => 1,
|
||||
'use_strict_mode' => 1
|
||||
);
|
||||
|
||||
$this->setOptions($options);
|
||||
|
||||
session_register_shutdown();
|
||||
|
||||
self::$instance = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return session_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
session_id($id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return session_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
session_name($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$allowedOptions = [
|
||||
'save_path' => true,
|
||||
'name' => true,
|
||||
'save_handler' => true,
|
||||
'gc_probability' => true,
|
||||
'gc_divisor' => true,
|
||||
'gc_maxlifetime' => true,
|
||||
'serialize_handler' => true,
|
||||
'cookie_lifetime' => true,
|
||||
'cookie_path' => true,
|
||||
'cookie_domain' => true,
|
||||
'cookie_secure' => true,
|
||||
'cookie_httponly' => true,
|
||||
'use_strict_mode' => true,
|
||||
'use_cookies' => true,
|
||||
'use_only_cookies' => true,
|
||||
'referer_check' => true,
|
||||
'cache_limiter' => true,
|
||||
'cache_expire' => true,
|
||||
'use_trans_sid' => true,
|
||||
'trans_sid_tags' => true, // PHP 7.1
|
||||
'trans_sid_hosts' => true, // PHP 7.1
|
||||
'sid_length' => true, // PHP 7.1
|
||||
'sid_bits_per_character' => true, // PHP 7.1
|
||||
'upload_progress.enabled' => true,
|
||||
'upload_progress.cleanup' => true,
|
||||
'upload_progress.prefix' => true,
|
||||
'upload_progress.name' => true,
|
||||
'upload_progress.freq' => true,
|
||||
'upload_progress.min-freq' => true,
|
||||
'lazy_write' => true,
|
||||
'url_rewriter.tags' => true, // Not used in PHP 7.1
|
||||
'hash_function' => true, // Not used in PHP 7.1
|
||||
'hash_bits_per_character' => true, // Not used in PHP 7.1
|
||||
'entropy_file' => true, // Not used in PHP 7.1
|
||||
'entropy_length' => true, // Not used in PHP 7.1
|
||||
];
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
// Allow nested options.
|
||||
foreach ($value as $key2 => $value2) {
|
||||
$ckey = "{$key}.{$key2}";
|
||||
if (isset($value2, $allowedOptions[$ckey])) {
|
||||
$this->ini_set("session.{$ckey}", $value2);
|
||||
}
|
||||
}
|
||||
} elseif (isset($value, $allowedOptions[$key])) {
|
||||
$this->ini_set("session.{$key}", $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function start($readonly = false)
|
||||
{
|
||||
// Protection against invalid session cookie names throwing exception: http://php.net/manual/en/function.session-id.php#116836
|
||||
if (isset($_COOKIE[session_name()]) && !preg_match('/^[-,a-zA-Z0-9]{1,128}$/', $_COOKIE[session_name()])) {
|
||||
unset($_COOKIE[session_name()]);
|
||||
}
|
||||
|
||||
$options = $this->options;
|
||||
if ($readonly) {
|
||||
$options['read_and_close'] = '1';
|
||||
}
|
||||
|
||||
$success = @session_start($options);
|
||||
if (!$success) {
|
||||
$last = error_get_last();
|
||||
$error = $last ? $last['message'] : 'Unknown error';
|
||||
throw new \RuntimeException('Failed to start session: ' . $error, 500);
|
||||
}
|
||||
|
||||
$params = session_get_cookie_params();
|
||||
|
||||
setcookie(
|
||||
session_name(),
|
||||
session_id(),
|
||||
time() + $params['lifetime'],
|
||||
$params['path'],
|
||||
$params['domain'],
|
||||
$params['secure'],
|
||||
$params['httponly']
|
||||
);
|
||||
|
||||
$this->started = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function invalidate()
|
||||
{
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(
|
||||
session_name(),
|
||||
'',
|
||||
time() - 42000,
|
||||
$params['path'],
|
||||
$params['domain'],
|
||||
$params['secure'],
|
||||
$params['httponly']
|
||||
);
|
||||
|
||||
if ($this->isSessionStarted()) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
$this->started = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if ($this->started) {
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
$this->started = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
session_unset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getAll()
|
||||
{
|
||||
return $_SESSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($_SESSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function isStarted()
|
||||
{
|
||||
return $this->started;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($_SESSION[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$_SESSION[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __unset($name)
|
||||
{
|
||||
unset($_SESSION[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://php.net/manual/en/function.session-status.php#113468
|
||||
* Check if session is started nicely.
|
||||
* @return bool
|
||||
*/
|
||||
protected function isSessionStarted()
|
||||
{
|
||||
return \PHP_SAPI !== 'cli' ? \PHP_SESSION_ACTIVE === session_status() : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected function ini_set($key, $value)
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
if (is_bool($value)) {
|
||||
$value = $value ? '1' : '0';
|
||||
}
|
||||
$value = (string)$value;
|
||||
}
|
||||
|
||||
$this->options[$key] = $value;
|
||||
ini_set($key, $value);
|
||||
}
|
||||
}
|
147
system/src/Grav/Framework/Session/SessionInterface.php
Normal file
147
system/src/Grav/Framework/Session/SessionInterface.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Session
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Session;
|
||||
|
||||
/**
|
||||
* Class Session
|
||||
* @package Grav\Framework\Session
|
||||
*/
|
||||
interface SessionInterface extends \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Get current session instance.
|
||||
*
|
||||
* @return Session
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function getInstance();
|
||||
|
||||
/**
|
||||
* Get session ID
|
||||
*
|
||||
* @return string|null Session ID
|
||||
*/
|
||||
public function getId();
|
||||
|
||||
/**
|
||||
* Set session ID
|
||||
*
|
||||
* @param string $id Session ID
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setId($id);
|
||||
|
||||
/**
|
||||
* Get session name
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Set session name
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name);
|
||||
|
||||
/**
|
||||
* Sets session.* ini variables.
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @see http://php.net/session.configuration
|
||||
*/
|
||||
public function setOptions(array $options);
|
||||
|
||||
/**
|
||||
* Starts the session storage
|
||||
*
|
||||
* @param bool $readonly
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function start($readonly = false);
|
||||
|
||||
/**
|
||||
* Invalidates the current session.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function invalidate();
|
||||
|
||||
/**
|
||||
* Force the session to be saved and closed
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function close();
|
||||
|
||||
/**
|
||||
* Free all session variables.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function clear();
|
||||
|
||||
/**
|
||||
* Returns all session variables.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAll();
|
||||
|
||||
/**
|
||||
* Retrieve an external iterator
|
||||
*
|
||||
* @return \ArrayIterator Return an ArrayIterator of $_SESSION
|
||||
*/
|
||||
public function getIterator();
|
||||
|
||||
/**
|
||||
* Checks if the session was started.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isStarted();
|
||||
|
||||
/**
|
||||
* Checks if session variable is defined.
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($name);
|
||||
|
||||
/**
|
||||
* Returns session variable.
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name);
|
||||
|
||||
/**
|
||||
* Sets session variable.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __set($name, $value);
|
||||
|
||||
/**
|
||||
* Removes session variable.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function __unset($name);
|
||||
}
|
213
system/src/Grav/Framework/Uri/Uri.php
Normal file
213
system/src/Grav/Framework/Uri/Uri.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Uri
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Uri;
|
||||
|
||||
use Grav\Framework\Psr7\AbstractUri;
|
||||
use GuzzleHttp\Psr7\Uri as GuzzleUri;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
/**
|
||||
* Implements PSR-7 UriInterface.
|
||||
*
|
||||
* @package Grav\Framework\Uri
|
||||
*/
|
||||
class Uri extends AbstractUri
|
||||
{
|
||||
/** @var array Array of Uri query. */
|
||||
private $queryParams;
|
||||
|
||||
/**
|
||||
* You can use `UriFactory` functions to create new `Uri` objects.
|
||||
*
|
||||
* @param array $parts
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $parts = [])
|
||||
{
|
||||
$this->initParts($parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return parent::getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPassword()
|
||||
{
|
||||
return parent::getPassword();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getParts()
|
||||
{
|
||||
return parent::getParts();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return parent::getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseUrl()
|
||||
{
|
||||
return parent::getBaseUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return string|null
|
||||
*/
|
||||
public function getQueryParam($key)
|
||||
{
|
||||
$queryParams = $this->getQueryParams();
|
||||
|
||||
return isset($queryParams[$key]) ? $queryParams[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function withoutQueryParam($key)
|
||||
{
|
||||
return GuzzleUri::withoutQueryValue($this, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string|null $value
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function withQueryParam($key, $value)
|
||||
{
|
||||
return GuzzleUri::withQueryValue($this, $key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getQueryParams()
|
||||
{
|
||||
if ($this->queryParams === null) {
|
||||
$this->queryParams = UriFactory::parseQuery($this->getQuery());
|
||||
}
|
||||
|
||||
return $this->queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function withQueryParams(array $params)
|
||||
{
|
||||
$query = UriFactory::buildQuery($params);
|
||||
|
||||
return $this->withQuery($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the URI has the default port of the current scheme.
|
||||
*
|
||||
* `$uri->getPort()` may return the standard port. This method can be used for some non-http/https Uri.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefaultPort()
|
||||
{
|
||||
return $this->getPort() === null || GuzzleUri::isDefaultPort($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the URI is absolute, i.e. it has a scheme.
|
||||
*
|
||||
* An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
|
||||
* if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
|
||||
* to another URI, the base URI. Relative references can be divided into several forms:
|
||||
* - network-path references, e.g. '//example.com/path'
|
||||
* - absolute-path references, e.g. '/path'
|
||||
* - relative-path references, e.g. 'subpath'
|
||||
*
|
||||
* @return bool
|
||||
* @link https://tools.ietf.org/html/rfc3986#section-4
|
||||
*/
|
||||
public function isAbsolute()
|
||||
{
|
||||
return GuzzleUri::isAbsolute($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the URI is a network-path reference.
|
||||
*
|
||||
* A relative reference that begins with two slash characters is termed an network-path reference.
|
||||
*
|
||||
* @return bool
|
||||
* @link https://tools.ietf.org/html/rfc3986#section-4.2
|
||||
*/
|
||||
public function isNetworkPathReference()
|
||||
{
|
||||
return GuzzleUri::isNetworkPathReference($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the URI is a absolute-path reference.
|
||||
*
|
||||
* A relative reference that begins with a single slash character is termed an absolute-path reference.
|
||||
*
|
||||
* @return bool
|
||||
* @link https://tools.ietf.org/html/rfc3986#section-4.2
|
||||
*/
|
||||
public function isAbsolutePathReference()
|
||||
{
|
||||
return GuzzleUri::isAbsolutePathReference($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the URI is a relative-path reference.
|
||||
*
|
||||
* A relative reference that does not begin with a slash character is termed a relative-path reference.
|
||||
*
|
||||
* @return bool
|
||||
* @link https://tools.ietf.org/html/rfc3986#section-4.2
|
||||
*/
|
||||
public function isRelativePathReference()
|
||||
{
|
||||
return GuzzleUri::isRelativePathReference($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the URI is a same-document reference.
|
||||
*
|
||||
* A same-document reference refers to a URI that is, aside from its fragment
|
||||
* component, identical to the base URI. When no base URI is given, only an empty
|
||||
* URI reference (apart from its fragment) is considered a same-document reference.
|
||||
*
|
||||
* @param UriInterface|null $base An optional base URI to compare against
|
||||
* @return bool
|
||||
* @link https://tools.ietf.org/html/rfc3986#section-4.4
|
||||
*/
|
||||
public function isSameDocumentReference(UriInterface $base = null)
|
||||
{
|
||||
return GuzzleUri::isSameDocumentReference($this, $base);
|
||||
}
|
||||
}
|
159
system/src/Grav/Framework/Uri/UriFactory.php
Normal file
159
system/src/Grav/Framework/Uri/UriFactory.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Uri
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Uri;
|
||||
|
||||
/**
|
||||
* Class Uri
|
||||
* @package Grav\Framework\Uri
|
||||
*/
|
||||
class UriFactory
|
||||
{
|
||||
/**
|
||||
* @param array $env
|
||||
* @return Uri
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function createFromEnvironment(array $env)
|
||||
{
|
||||
return new Uri(static::parseUrlFromEnvironment($env));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @return Uri
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function createFromString($uri)
|
||||
{
|
||||
return new Uri(static::parseUrl($uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a URI from a array of `parse_url()` components.
|
||||
*
|
||||
* @param array $parts
|
||||
* @return Uri
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function createFromParts(array $parts)
|
||||
{
|
||||
return new Uri($parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $env
|
||||
* @return array
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function parseUrlFromEnvironment(array $env)
|
||||
{
|
||||
// Build scheme.
|
||||
if (isset($env['REQUEST_SCHEME'])) {
|
||||
$scheme = strtolower($env['REQUEST_SCHEME']);
|
||||
} else {
|
||||
$https = isset($env['HTTPS']) ? $env['HTTPS'] : '';
|
||||
$scheme = (empty($https) || strtolower($https) === 'off') ? 'http' : 'https';
|
||||
}
|
||||
|
||||
// Build user and password.
|
||||
$user = isset($env['PHP_AUTH_USER']) ? $env['PHP_AUTH_USER'] : '';
|
||||
$pass = isset($env['PHP_AUTH_PW']) ? $env['PHP_AUTH_PW'] : '';
|
||||
|
||||
// Build host.
|
||||
$host = 'localhost';
|
||||
if (isset($env['HTTP_HOST'])) {
|
||||
$host = $env['HTTP_HOST'];
|
||||
} elseif (isset($env['SERVER_NAME'])) {
|
||||
$host = $env['SERVER_NAME'];
|
||||
}
|
||||
// Remove port from HTTP_HOST generated $hostname
|
||||
$host = explode(':', $host)[0];
|
||||
|
||||
// Build port.
|
||||
$port = isset($env['SERVER_PORT']) ? (int)$env['SERVER_PORT'] : null;
|
||||
|
||||
// Build path.
|
||||
$request_uri = isset($env['REQUEST_URI']) ? $env['REQUEST_URI'] : '';
|
||||
$path = parse_url('http://example.com' . $request_uri, PHP_URL_PATH);
|
||||
|
||||
// Build query string.
|
||||
$query = isset($env['QUERY_STRING']) ? $env['QUERY_STRING'] : '';
|
||||
if ($query === '') {
|
||||
$query = parse_url('http://example.com' . $request_uri, PHP_URL_QUERY);
|
||||
}
|
||||
|
||||
// Support ngnix routes.
|
||||
if (strpos($query, '_url=') === 0) {
|
||||
parse_str($query, $q);
|
||||
unset($q['_url']);
|
||||
$query = http_build_query($q);
|
||||
}
|
||||
|
||||
return [
|
||||
'scheme' => $scheme,
|
||||
'user' => $user,
|
||||
'pass' => $pass,
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'path' => $path,
|
||||
'query' => $query
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* UTF-8 aware parse_url() implementation.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function parseUrl($url)
|
||||
{
|
||||
if (!is_string($url)) {
|
||||
throw new \InvalidArgumentException('URL must be a string');
|
||||
}
|
||||
|
||||
$encodedUrl = preg_replace_callback(
|
||||
'%[^:/@?&=#]+%u',
|
||||
function ($matches) { return rawurlencode($matches[0]); },
|
||||
$url
|
||||
);
|
||||
|
||||
$parts = parse_url($encodedUrl);
|
||||
if ($parts === false) {
|
||||
throw new \InvalidArgumentException('Malformed URL: ' . $encodedUrl);
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse query string and return it as an array.
|
||||
*
|
||||
* @param string $query
|
||||
* @return mixed
|
||||
*/
|
||||
public static function parseQuery($query)
|
||||
{
|
||||
parse_str($query, $params);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query string from variables.
|
||||
*
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
public static function buildQuery(array $params)
|
||||
{
|
||||
return $params ? http_build_query($params, null, ini_get('arg_separator.output'), PHP_QUERY_RFC3986) : '';
|
||||
}
|
||||
}
|
140
system/src/Grav/Framework/Uri/UriPartsFilter.php
Normal file
140
system/src/Grav/Framework/Uri/UriPartsFilter.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Uri
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Uri;
|
||||
|
||||
/**
|
||||
* Class Uri
|
||||
* @package Grav\Framework\Uri
|
||||
*/
|
||||
class UriPartsFilter
|
||||
{
|
||||
const HOSTNAME_REGEX = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/u';
|
||||
|
||||
/**
|
||||
* @param string $scheme
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException If the scheme is invalid.
|
||||
*/
|
||||
public static function filterScheme($scheme)
|
||||
{
|
||||
if (!is_string($scheme)) {
|
||||
throw new \InvalidArgumentException('Uri scheme must be a string');
|
||||
}
|
||||
|
||||
return strtolower($scheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the user info string.
|
||||
*
|
||||
* @param string $info The raw user or password.
|
||||
* @return string The percent-encoded user or password string.
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function filterUserInfo($info)
|
||||
{
|
||||
if (!is_string($info)) {
|
||||
throw new \InvalidArgumentException('Uri user info must be a string');
|
||||
}
|
||||
|
||||
return preg_replace_callback(
|
||||
'/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=]+|%(?![A-Fa-f0-9]{2}))/u',
|
||||
function ($match) {
|
||||
return rawurlencode($match[0]);
|
||||
},
|
||||
$info
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $host
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException If the host is invalid.
|
||||
*/
|
||||
public static function filterHost($host)
|
||||
{
|
||||
if (!is_string($host)) {
|
||||
throw new \InvalidArgumentException('Uri host must be a string');
|
||||
}
|
||||
|
||||
if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$host = '[' . $host . ']';
|
||||
} elseif ($host && preg_match(static::HOSTNAME_REGEX, $host) !== 1) {
|
||||
throw new \InvalidArgumentException('Uri host name validation failed');
|
||||
}
|
||||
|
||||
return strtolower($host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Uri port.
|
||||
*
|
||||
* This method
|
||||
*
|
||||
* @param int|null $port
|
||||
* @return int|null
|
||||
* @throws \InvalidArgumentException If the port is invalid.
|
||||
*/
|
||||
public static function filterPort($port = null)
|
||||
{
|
||||
if (null === $port || (is_int($port) && ($port >= 1 && $port <= 65535))) {
|
||||
return $port;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Uri port must be null or an integer between 1 and 65535');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Uri path.
|
||||
*
|
||||
* This method percent-encodes all reserved characters in the provided path string. This method
|
||||
* will NOT double-encode characters that are already percent-encoded.
|
||||
*
|
||||
* @param string $path The raw uri path.
|
||||
* @return string The RFC 3986 percent-encoded uri path.
|
||||
* @throws \InvalidArgumentException If the path is invalid.
|
||||
* @link http://www.faqs.org/rfcs/rfc3986.html
|
||||
*/
|
||||
public static function filterPath($path)
|
||||
{
|
||||
if (!is_string($path)) {
|
||||
throw new \InvalidArgumentException('Uri path must be a string');
|
||||
}
|
||||
|
||||
return preg_replace_callback(
|
||||
'/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/u',
|
||||
function ($match) {
|
||||
return rawurlencode($match[0]);
|
||||
},
|
||||
$path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the query string or fragment of a URI.
|
||||
*
|
||||
* @param string $query The raw uri query string.
|
||||
* @return string The percent-encoded query string.
|
||||
* @throws \InvalidArgumentException If the query is invalid.
|
||||
*/
|
||||
public static function filterQueryOrFragment($query)
|
||||
{
|
||||
if (!is_string($query)) {
|
||||
throw new \InvalidArgumentException('Uri query string and fragment must be a string');
|
||||
}
|
||||
|
||||
return preg_replace_callback(
|
||||
'/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/u',
|
||||
function ($match) {
|
||||
return rawurlencode($match[0]);
|
||||
},
|
||||
$query
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user