first commit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
parent::__construct($namespace, $defaultLifetime ?: 31557600); // = 1 year
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
private function init($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;
|
||||
}
|
||||
|
||||
if (!file_exists($directory)) {
|
||||
@mkdir($directory, 0777, true);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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()
|
||||
{
|
||||
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
|
||||
if (!method_exists($this, 'createFrom')) {
|
||||
return new static(array_reverse($this->toArray()));
|
||||
}
|
||||
|
||||
return $this->createFrom(array_reverse($this->toArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffle items.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function shuffle()
|
||||
{
|
||||
$keys = $this->getKeys();
|
||||
shuffle($keys);
|
||||
|
||||
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
|
||||
if (!method_exists($this, 'createFrom')) {
|
||||
return new static(array_replace(array_flip($keys), $this->toArray()));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?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 = [];
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @return static
|
||||
*/
|
||||
public static function create($id = null)
|
||||
{
|
||||
return new static($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $serialized
|
||||
* @return ContentBlockInterface
|
||||
*/
|
||||
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 \RuntimeException('Bad data');
|
||||
}
|
||||
|
||||
/** @var ContentBlockInterface $instance */
|
||||
$instance = new $type($id);
|
||||
$instance->build($serialized);
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException(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->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();
|
||||
|
||||
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 $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']) ? (string) $serialized['_version'] : '1';
|
||||
if ($version !== $this->version) {
|
||||
throw new \RuntimeException(sprintf('Unsupported version %s', $version));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?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 $content
|
||||
* @return $this
|
||||
*/
|
||||
public function setContent($content);
|
||||
|
||||
/**
|
||||
* @param ContentBlockInterface $block
|
||||
* @return $this
|
||||
*/
|
||||
public function addBlock(ContentBlockInterface $block);
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
<?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 $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,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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
|
||||
if (!method_exists($this, 'createFrom')) {
|
||||
return new static($list);
|
||||
}
|
||||
|
||||
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) {
|
||||
// TODO: remove when PHP 5.6 is minimum (with doctrine/collections v1.4).
|
||||
if (!method_exists($this, 'createFrom')) {
|
||||
$collection = new static($elements);
|
||||
} else {
|
||||
$collection = $this->createFrom($elements);
|
||||
}
|
||||
|
||||
$collections[$id] = $collection;
|
||||
}
|
||||
|
||||
return $collections;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Traversable
|
||||
*/
|
||||
abstract public function getIterator();
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?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
|
||||
{
|
||||
static protected $prefix;
|
||||
static protected $type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_key;
|
||||
|
||||
/**
|
||||
* @param bool $prefix
|
||||
* @return string
|
||||
*/
|
||||
public function getType($prefix = true)
|
||||
{
|
||||
if (static::$type) {
|
||||
return ($prefix ? static::$prefix : '') . static::$type;
|
||||
}
|
||||
|
||||
$class = get_class($this);
|
||||
return ($prefix ? static::$prefix : '') . 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->jsonSerialize());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $serialized
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
$data = unserialize($serialized);
|
||||
|
||||
if (method_exists($this, 'initObjectProperties')) {
|
||||
$this->initObjectProperties();
|
||||
}
|
||||
$this->doUnserialize($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
protected function setKey($key)
|
||||
{
|
||||
$this->_key = (string) $key;
|
||||
}
|
||||
|
||||
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,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,66 @@
|
||||
<?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 Grav\Framework\Collection\CollectionInterface;
|
||||
|
||||
/**
|
||||
* ObjectCollection Interface
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
interface ObjectCollectionInterface extends CollectionInterface, 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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\Collection\ArrayCollection;
|
||||
use Grav\Framework\Object\Access\NestedPropertyCollectionTrait;
|
||||
use Grav\Framework\Object\Base\ObjectCollectionTrait;
|
||||
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);
|
||||
}
|
||||
|
||||
protected function getElements()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
protected function setElements(array $elements)
|
||||
{
|
||||
return $elements;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
<?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, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
$newValue = null !== $value ? (string)$value : null;
|
||||
|
||||
if ($oldValue === $newValue) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$new = clone $this;
|
||||
if ($newValue === null) {
|
||||
unset($new->{$type}[$param]);
|
||||
} else {
|
||||
$new->{$type}[$param] = $newValue;
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?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 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) : '';
|
||||
}
|
||||
}
|
||||
@@ -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