mirror of
https://github.com/getgrav/grav.git
synced 2026-07-19 19:00:24 +02:00
Merge branch 'feature/objects' of https://github.com/getgrav/grav into 2.0
This commit is contained in:
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,3 +1,18 @@
|
||||
# Feature/Objects Branch
|
||||
## mm/dd/2017
|
||||
|
||||
1. [](#new)
|
||||
* Added `Pages::baseUrl()`, `Pages::homeUrl()` and `Pages::url()` functions
|
||||
* Added `Debugger::getCaller()` to figure out where the method was called from
|
||||
* Added support for custom output providers like Slim Framework
|
||||
* Added `Grav\Framework\Collection` classes for creating collections
|
||||
* Added `Grav\Framework\ContentBlock` classes which add better support for nested HTML blocks with assets
|
||||
* Added `Grav\Framework\Object` classes to support general objects and their collections
|
||||
* Deprecated GravTrait
|
||||
1. [](#improved)
|
||||
* Improve error handling in Folder::move()
|
||||
* Added extra parameter for Twig::processSite() to include custom context
|
||||
|
||||
# v1.2.5
|
||||
## 04/xx/2017
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"symfony/var-dumper": "~2.8",
|
||||
"symfony/polyfill-iconv": "~1.0",
|
||||
"doctrine/cache": "~1.5",
|
||||
"doctrine/collections": "^1.4",
|
||||
"filp/whoops": "~2.0",
|
||||
"matthiasmullie/minify": "^1.3",
|
||||
"monolog/monolog": "~1.0",
|
||||
|
||||
69
composer.lock
generated
69
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e981b23b44354d081ec1e1ac214656a1",
|
||||
"content-hash": "acef808d0660c4938b26a2348ca26191",
|
||||
"packages": [
|
||||
{
|
||||
"name": "antoligy/dom-string-iterators",
|
||||
@@ -120,6 +120,73 @@
|
||||
],
|
||||
"time": "2016-10-29T11:16:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/collections",
|
||||
"version": "v1.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/collections.git",
|
||||
"reference": "1a4fb7e902202c33cce8c55989b945612943c2ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba",
|
||||
"reference": "1a4fb7e902202c33cce8c55989b945612943c2ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.6 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "~0.1@dev",
|
||||
"phpunit/phpunit": "^5.7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Doctrine\\Common\\Collections\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Collections Abstraction library",
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"keywords": [
|
||||
"array",
|
||||
"collections",
|
||||
"iterator"
|
||||
],
|
||||
"time": "2017-01-03T10:49:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "donatj/phpuseragentparser",
|
||||
"version": "v0.7.0",
|
||||
|
||||
@@ -120,6 +120,13 @@ class Debugger
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCaller($ignore = 2)
|
||||
{
|
||||
$trace = debug_backtrace(false, $ignore);
|
||||
|
||||
return array_pop($trace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a data collector
|
||||
*
|
||||
|
||||
@@ -332,7 +332,8 @@ abstract class Folder
|
||||
*/
|
||||
public static function move($source, $target)
|
||||
{
|
||||
if (!is_dir($source)) {
|
||||
if (!file_exists($source) || !is_dir($source)) {
|
||||
// Rename fails if source folder does not exist.
|
||||
throw new \RuntimeException('Cannot move non-existing folder.');
|
||||
}
|
||||
|
||||
@@ -341,11 +342,24 @@ abstract class Folder
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists($target)) {
|
||||
// Rename fails if target folder exists.
|
||||
throw new \RuntimeException('Cannot move files to existing folder/file.');
|
||||
}
|
||||
|
||||
// Make sure that path to the target exists before moving.
|
||||
self::create(dirname($target));
|
||||
|
||||
$success = @rename($source, $target);
|
||||
if (!$success) {
|
||||
// Silence warnings (chmod failed etc).
|
||||
@rename($source, $target);
|
||||
|
||||
// Rename function can fail while still succeeding, so let's check if the folder exists.
|
||||
if (!file_exists($target) || !is_dir($target)) {
|
||||
// In some rare cases rename() creates file, not a folder. Get rid of it.
|
||||
if (file_exists($target)) {
|
||||
@unlink($target);
|
||||
}
|
||||
// Rename doesn't support moving folders across filesystems. Use copy instead.
|
||||
self::copy($source, $target);
|
||||
self::delete($source);
|
||||
}
|
||||
@@ -353,6 +367,7 @@ abstract class Folder
|
||||
// Make sure that the change will be detected when caching.
|
||||
@touch(dirname($source));
|
||||
@touch(dirname($target));
|
||||
@touch($target);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -116,9 +116,6 @@ class Grav extends Container
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
|
||||
// process all processors (e.g. config, initialize, assets, ..., render)
|
||||
foreach ($this->processors as $processor) {
|
||||
$processor = $this[$processor];
|
||||
@@ -127,14 +124,10 @@ class Grav extends Container
|
||||
});
|
||||
}
|
||||
|
||||
// Set the header type
|
||||
$this->header();
|
||||
|
||||
echo $this->output;
|
||||
/** @var Debugger $debugger */
|
||||
$debugger = $this['debugger'];
|
||||
$debugger->render();
|
||||
|
||||
$this->fireEvent('onOutputRendered');
|
||||
|
||||
register_shutdown_function([$this, 'shutdown']);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* @deprecated Remove in Grav 2.0
|
||||
*/
|
||||
trait GravTrait
|
||||
{
|
||||
protected static $grav;
|
||||
@@ -20,6 +23,10 @@ trait GravTrait
|
||||
if (!self::$grav) {
|
||||
self::$grav = Grav::instance();
|
||||
}
|
||||
|
||||
$caller = self::$grav['debugger']->getCaller();
|
||||
self::$grav['debugger']->addMessage("Deprecated GravTrait used in {$caller['file']}", 'deprecated');
|
||||
|
||||
return self::$grav;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,12 @@ class Pages
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $base;
|
||||
protected $base = '';
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected $baseUrl = [];
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
@@ -100,7 +105,6 @@ class Pages
|
||||
public function __construct(Grav $c)
|
||||
{
|
||||
$this->grav = $c;
|
||||
$this->base = '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,11 +119,82 @@ class Pages
|
||||
if ($path !== null) {
|
||||
$path = trim($path, '/');
|
||||
$this->base = $path ? '/' . $path : null;
|
||||
$this->baseUrl = [];
|
||||
}
|
||||
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get base URL for Grav pages.
|
||||
*
|
||||
* @param string $lang Optional language code for multilingual links.
|
||||
* @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function baseUrl($lang = null, $absolute = null)
|
||||
{
|
||||
$lang = (string) $lang;
|
||||
$type = $absolute === null ? 'base_url' : ($absolute ? 'base_url_absolute' : 'base_url_relative');
|
||||
$key = "{$lang} {$type}";
|
||||
|
||||
if (!isset($this->baseUrl[$key])) {
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
if (!$lang) {
|
||||
$lang = $language->getActive();
|
||||
}
|
||||
|
||||
$path_append = rtrim($this->grav['pages']->base(), '/');
|
||||
if ($language->getDefault() != $lang || $config->get('system.languages.include_default_lang') === true) {
|
||||
$path_append .= $lang ? '/' . $lang : '';
|
||||
}
|
||||
|
||||
$this->baseUrl[$key] = $this->grav[$type] . $path_append;
|
||||
}
|
||||
|
||||
return $this->baseUrl[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get home URL for Grav site.
|
||||
*
|
||||
* @param string $lang Optional language code for multilingual links.
|
||||
* @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function homeUrl($lang = null, $absolute = null)
|
||||
{
|
||||
return $this->baseUrl($lang, $absolute) ?: '/';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get home URL for Grav site.
|
||||
*
|
||||
* @param string $route Optional route to the page.
|
||||
* @param string $lang Optional language code for multilingual links.
|
||||
* @param bool $absolute If true, return absolute url, if false, return relative url. Otherwise return default.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function url($route = '/', $lang = null, $absolute = null)
|
||||
{
|
||||
if ($route === '/') {
|
||||
return $this->homeUrl($lang, $absolute);
|
||||
}
|
||||
|
||||
return $this->baseUrl($lang, $absolute) . $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class initialization. Must be called before using this class.
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,6 @@ class ConfigurationProcessor extends ProcessorBase implements ProcessorInterface
|
||||
public function process()
|
||||
{
|
||||
$this->container['config']->init();
|
||||
return $this->container['plugins']->setup();
|
||||
$this->container['plugins']->setup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Grav\Common\Processors;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
|
||||
class ProcessorBase
|
||||
abstract class ProcessorBase
|
||||
{
|
||||
/**
|
||||
* @var Grav
|
||||
|
||||
@@ -15,7 +15,22 @@ class RenderProcessor extends ProcessorBase implements ProcessorInterface
|
||||
|
||||
public function process()
|
||||
{
|
||||
$this->container->output = $this->container['output'];
|
||||
$this->container->fireEvent('onOutputGenerated');
|
||||
$container = $this->container;
|
||||
$output = $container['output'];
|
||||
|
||||
if ($output instanceof \Psr\Http\Message\ResponseInterface) {
|
||||
// Support for custom output providers like Slim Framework.
|
||||
} else {
|
||||
// Use internal Grav output.
|
||||
$container->output = $output;
|
||||
$container->fireEvent('onOutputGenerated');
|
||||
|
||||
// Set the header type
|
||||
$container->header();
|
||||
|
||||
echo $output;
|
||||
}
|
||||
|
||||
$container->fireEvent('onOutputRendered');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
namespace Grav\Common\Service;
|
||||
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Twig\Twig;
|
||||
use Pimple\Container;
|
||||
use Pimple\ServiceProviderInterface;
|
||||
|
||||
@@ -16,7 +18,13 @@ class OutputServiceProvider implements ServiceProviderInterface
|
||||
public function register(Container $container)
|
||||
{
|
||||
$container['output'] = function ($c) {
|
||||
return $c['twig']->processSite($c['page']->templateFormat());
|
||||
/** @var Twig $twig */
|
||||
$twig = $c['twig'];
|
||||
|
||||
/** @var Page $page */
|
||||
$page = $c['page'];
|
||||
|
||||
return $twig->processSite($page->templateFormat());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use Grav\Common\Config\Config;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Language\LanguageCodes;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Page\Pages;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
|
||||
@@ -84,11 +85,6 @@ class Twig
|
||||
|
||||
$active_language = $language->getActive();
|
||||
|
||||
$path_append = rtrim($this->grav['pages']->base(), '/');
|
||||
if ($language->getDefault() != $active_language || $config->get('system.languages.include_default_lang') === true) {
|
||||
$path_append .= $active_language ? '/' . $active_language : '';
|
||||
}
|
||||
|
||||
// handle language templates if available
|
||||
if ($language->enabled()) {
|
||||
$lang_templates = $locator->findResource('theme://templates/' . ($active_language ? $active_language : $language->getDefault()));
|
||||
@@ -153,7 +149,8 @@ class Twig
|
||||
|
||||
$this->grav->fireEvent('onTwigExtensions');
|
||||
|
||||
$base_url = $this->grav['base_url'] . $path_append;
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
|
||||
// Set some standard variables for twig
|
||||
$this->twig_vars = $this->twig_vars + [
|
||||
@@ -166,11 +163,11 @@ class Twig
|
||||
'taxonomy' => $this->grav['taxonomy'],
|
||||
'browser' => $this->grav['browser'],
|
||||
'base_dir' => rtrim(ROOT_DIR, '/'),
|
||||
'base_url' => $base_url,
|
||||
'home_url' => $pages->homeUrl($active_language),
|
||||
'base_url' => $pages->baseUrl($active_language),
|
||||
'base_url_absolute' => $pages->baseUrl($active_language, true),
|
||||
'base_url_relative' => $pages->baseUrl($active_language, false),
|
||||
'base_url_simple' => $this->grav['base_url'],
|
||||
'base_url_absolute' => $this->grav['base_url_absolute'] . $path_append,
|
||||
'base_url_relative' => $this->grav['base_url_relative'] . $path_append,
|
||||
'home_url' => $base_url == '' ? '/' : $base_url,
|
||||
'theme_dir' => $locator->findResource('theme://'),
|
||||
'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false),
|
||||
'html_lang' => $this->grav['language']->getActive() ?: $config->get('site.default_lang', 'en'),
|
||||
@@ -315,7 +312,7 @@ class Twig
|
||||
* @return string the rendered output
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function processSite($format = null)
|
||||
public function processSite($format = null, array $vars = [])
|
||||
{
|
||||
// set the page now its been processed
|
||||
$this->grav->fireEvent('onTwigSiteVariables');
|
||||
@@ -343,7 +340,7 @@ class Twig
|
||||
$template = $this->template($page->template() . $ext);
|
||||
|
||||
try {
|
||||
$output = $this->twig->render($template, $twig_vars);
|
||||
$output = $this->twig->render($template, $vars + $twig_vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
$error_msg = $e->getMessage();
|
||||
// Try html version of this template if initial template was NOT html
|
||||
|
||||
@@ -49,7 +49,7 @@ class Group extends Data
|
||||
*
|
||||
* @param string $groupname
|
||||
*
|
||||
* @return object
|
||||
* @return bool
|
||||
*/
|
||||
public static function groupExists($groupname)
|
||||
{
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace Grav\Console;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Composer;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Console\Cli\ClearCacheCommand;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
@@ -20,8 +19,6 @@ use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
trait ConsoleTrait
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, 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 jsonSerialize()
|
||||
{
|
||||
$this->initialize();
|
||||
return $this->collection->jsonSerialize();
|
||||
}
|
||||
}
|
||||
52
system/src/Grav/Framework/Collection/ArrayCollection.php
Normal file
52
system/src/Grav/Framework/Collection/ArrayCollection.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Collection;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection as BaseArrayCollection;
|
||||
|
||||
/**
|
||||
* General JSON serializable collection.
|
||||
*
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
class ArrayCollection extends BaseArrayCollection implements CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Reverse the order of the items.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function reverse()
|
||||
{
|
||||
return $this->createFrom(array_reverse($this->toArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffle items.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function shuffle()
|
||||
{
|
||||
$keys = $this->getKeys();
|
||||
shuffle($keys);
|
||||
|
||||
return $this->createFrom(array_replace(array_flip($keys), $this->toArray()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementes JsonSerializable interface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
33
system/src/Grav/Framework/Collection/CollectionInterface.php
Normal file
33
system/src/Grav/Framework/Collection/CollectionInterface.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, 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();
|
||||
}
|
||||
278
system/src/Grav/Framework/Collection/FileCollection.php
Normal file
278
system/src/Grav/Framework/Collection/FileCollection.php
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Collection
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, 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 FileCollection extends AbstractLazyCollection
|
||||
{
|
||||
const INCLUDE_FILES = 1;
|
||||
const INCLUDE_FOLDERS = 2;
|
||||
const RECURSIVE = 4;
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @param int $flags
|
||||
*/
|
||||
public function __construct($path, $flags = null)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->flags = (int) ($flags ?: FileCollection::INCLUDE_FILES | FileCollection::INCLUDE_FOLDERS | FileCollection::RECURSIVE);
|
||||
|
||||
$this->setIterator();
|
||||
$this->setFilter();
|
||||
$this->setObjectBuilder();
|
||||
$this->setNestingLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFlags()
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getNestingLimit()
|
||||
{
|
||||
return $this->nestingLimit;
|
||||
}
|
||||
|
||||
public 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|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)
|
||||
{
|
||||
if ($this->filterFunction) {
|
||||
$oldFilterFunction = $this->filterFunction;
|
||||
$this->filterFunction = function ($expr) use ($oldFilterFunction, $filterFunction) {
|
||||
return $oldFilterFunction($expr) && $filterFunction($expr);
|
||||
};
|
||||
} else {
|
||||
$this->filterFunction = $filterFunction;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|null $objectFunction
|
||||
* @return $this
|
||||
*/
|
||||
public function setObjectBuilder(callable $objectFunction = null)
|
||||
{
|
||||
$this->createObjectFunction = $objectFunction ?: [$this, 'createObject'];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setNestingLimit($limit = 99)
|
||||
{
|
||||
$this->nestingLimit = (int) $limit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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()
|
||||
];
|
||||
}
|
||||
}
|
||||
229
system/src/Grav/Framework/ContentBlock/ContentBlock.php
Normal file
229
system/src/Grav/Framework/ContentBlock/ContentBlock.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, 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
|
||||
*/
|
||||
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) 2014 - 2017 RocketTheme, 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);
|
||||
}
|
||||
374
system/src/Grav/Framework/ContentBlock/HtmlBlock.php
Normal file
374
system/src/Grav/Framework/ContentBlock/HtmlBlock.php
Normal file
@@ -0,0 +1,374 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\ContentBlock;
|
||||
|
||||
/**
|
||||
* HtmlBlock
|
||||
*
|
||||
* @package Grav\Framework\ContentBlock
|
||||
*/
|
||||
class HtmlBlock extends ContentBlock implements HtmlBlockInterface
|
||||
{
|
||||
protected $version = 1;
|
||||
protected $frameworks = [];
|
||||
protected $styles = [];
|
||||
protected $scripts = [];
|
||||
protected $html = [];
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
$assets = $this->getAssetsFast();
|
||||
|
||||
$this->sortAssets($assets['styles']);
|
||||
$this->sortAssets($assets['scripts']);
|
||||
$this->sortAssets($assets['html']);
|
||||
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFrameworks()
|
||||
{
|
||||
$assets = $this->getAssetsFast();
|
||||
|
||||
return array_keys($assets['frameworks']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
public function getStyles($location = 'head')
|
||||
{
|
||||
return $this->getAssetsInLocation('styles', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
public function getScripts($location = 'head')
|
||||
{
|
||||
return $this->getAssetsInLocation('scripts', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @return array
|
||||
*/
|
||||
public function getHtml($location = 'bottom')
|
||||
{
|
||||
return $this->getAssetsInLocation('html', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$array = parent::toArray();
|
||||
|
||||
if ($this->frameworks) {
|
||||
$array['frameworks'] = $this->frameworks;
|
||||
}
|
||||
if ($this->styles) {
|
||||
$array['styles'] = $this->styles;
|
||||
}
|
||||
if ($this->scripts) {
|
||||
$array['scripts'] = $this->scripts;
|
||||
}
|
||||
if ($this->html) {
|
||||
$array['html'] = $this->html;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $serialized
|
||||
*/
|
||||
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;
|
||||
}
|
||||
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) 2014 - 2017 RocketTheme, 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');
|
||||
}
|
||||
185
system/src/Grav/Framework/Object/AbstractObject.php
Normal file
185
system/src/Grav/Framework/Object/AbstractObject.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
use RocketTheme\Toolbox\ArrayTraits\ArrayAccessWithGetters;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Export;
|
||||
|
||||
/**
|
||||
* Abstract base class for stored objects.
|
||||
*
|
||||
* @property string $id
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
abstract class AbstractObject implements ObjectInterface, StoredObjectInterface
|
||||
{
|
||||
use ObjectStorageTrait {
|
||||
check as traitcheck;
|
||||
}
|
||||
use ArrayAccessWithGetters, Export;
|
||||
|
||||
/**
|
||||
* Primary key for the object.
|
||||
* @var array
|
||||
*/
|
||||
static protected $primaryKey = [
|
||||
'id' => null
|
||||
];
|
||||
|
||||
/**
|
||||
* Default properties for the object.
|
||||
* @var array
|
||||
*/
|
||||
static protected $defaults = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
static protected $collectionClass = 'Grav\\Framework\\Object\\AbstractObjectCollection';
|
||||
|
||||
/**
|
||||
* Properties of the object.
|
||||
* @var array
|
||||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @param array $ids List of primary Ids or null to return everything that has been loaded.
|
||||
* @param bool $readonly
|
||||
* @return AbstractObjectCollection
|
||||
*/
|
||||
static public function instances(array $ids = null, $readonly = true)
|
||||
{
|
||||
$collectionClass = static::$collectionClass;
|
||||
|
||||
if (is_null($ids)) {
|
||||
return new $collectionClass(static::$instances);
|
||||
}
|
||||
|
||||
if (empty($ids)) {
|
||||
return new $collectionClass([]);
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$list = [];
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if (!isset(static::$instances[$id])) {
|
||||
$list[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
if ($list) {
|
||||
$c = get_called_class();
|
||||
$storage = static::getStorage();
|
||||
$list = $storage->loadList($list);
|
||||
foreach ($list as $keys) {
|
||||
/** @var static $instance */
|
||||
$instance = new $c();
|
||||
$instance->doLoad($keys);
|
||||
$id = $instance->getId();
|
||||
if ($id && !isset(static::$instances[$id])) {
|
||||
$instance->initialize();
|
||||
static::$instances[$id] = $instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if (isset(static::$instances[$id])) {
|
||||
$results[$id] = $readonly ? clone static::$instances[$id] : static::$instances[$id];
|
||||
}
|
||||
}
|
||||
|
||||
return new $collectionClass($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to perform sanity checks on the instance properties to ensure they are safe to store in the storage.
|
||||
*
|
||||
* Child classes should override this method to make sure the data they are storing in the storage is safe and as
|
||||
* expected before saving the object.
|
||||
*
|
||||
* @return bool True if the instance is sane and able to be stored in the storage.
|
||||
*/
|
||||
public function check($includeChildren = false)
|
||||
{
|
||||
return $this->checkKeys() && $this->traitCheck($includeChildren);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return array
|
||||
*/
|
||||
public function getKeys(array $keys = [])
|
||||
{
|
||||
foreach (static::$primaryKey as $key => $value) {
|
||||
if (!isset($keys[$key])) {
|
||||
if (isset($this->items[$key])) {
|
||||
$keys[$key] = $this->items[$key];
|
||||
} else {
|
||||
$keys[$key] = $value;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return bool
|
||||
*/
|
||||
public function checkKeys(array $keys = [])
|
||||
{
|
||||
if (!$keys) {
|
||||
$keys = $this->getKeys();
|
||||
}
|
||||
|
||||
foreach ($keys as $key => $value) {
|
||||
if ($value === null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementes JsonSerializable interface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return __CLASS__ . '@' . spl_object_hash($this);
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
/**
|
||||
* @param array $items
|
||||
* @param array $keys
|
||||
*/
|
||||
protected function doLoad(array $items, array $keys = [])
|
||||
{
|
||||
$this->items = array_replace(static::$defaults, $this->items, $this->getKeys($keys), $items);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
use Grav\Framework\Collection\ArrayCollection;
|
||||
|
||||
/**
|
||||
* Abstract Object Collection
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
abstract class AbstractObjectCollection extends ArrayCollection implements ObjectCollectionInterface, StoredObjectInterface
|
||||
{
|
||||
use ObjectStorageTrait {
|
||||
getId as getParentId;
|
||||
}
|
||||
use ObjectCollectionTrait;
|
||||
|
||||
protected $id;
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id ?: $this->getParentId();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
use Grav\Framework\Collection\CollectionInterface;
|
||||
|
||||
/**
|
||||
* ObjectCollection Interface
|
||||
* @package Grav\Framework\Collection
|
||||
*/
|
||||
interface ObjectCollectionInterface extends CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Create a copy from this collection by cloning all objects in the collection.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy();
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @return array Values of the property.
|
||||
*/
|
||||
public function getProperty($property);
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $value New value.
|
||||
* @return $this
|
||||
*/
|
||||
public function setProperty($property, $value);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
96
system/src/Grav/Framework/Object/ObjectCollectionTrait.php
Normal file
96
system/src/Grav/Framework/Object/ObjectCollectionTrait.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
/**
|
||||
* ObjectCollection Trait
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
trait ObjectCollectionTrait
|
||||
{
|
||||
/**
|
||||
* @param array $elements
|
||||
* @return static
|
||||
*/
|
||||
abstract public function createFrom(array $elements);
|
||||
|
||||
/**
|
||||
* Create a copy from this collection by cloning all objects in the collection.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this as $key => $value) {
|
||||
$list[$key] = is_object($value) ? clone $value : $value;
|
||||
}
|
||||
|
||||
return $this->createFrom($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be fetched.
|
||||
* @return array Key/Value pairs of the properties.
|
||||
*/
|
||||
public function getProperty($property)
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this as $id => $element) {
|
||||
$list[$id] = isset($element->{$property}) ? $element->{$property} : null;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property Object property to be updated.
|
||||
* @param string $value New value.
|
||||
*/
|
||||
public function setProperty($property, $value)
|
||||
{
|
||||
foreach ($this as $element) {
|
||||
$element->{$property} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 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.
|
||||
*
|
||||
* @param string $property
|
||||
* @return array
|
||||
*/
|
||||
public function group($property)
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this as $element) {
|
||||
$list[$element->{$property}][] = $element;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
62
system/src/Grav/Framework/Object/ObjectInterface.php
Normal file
62
system/src/Grav/Framework/Object/ObjectInterface.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
/**
|
||||
* Object Interface
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
interface ObjectInterface extends \ArrayAccess, \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Returns the global instance to the object.
|
||||
*
|
||||
* Note that using array of fields will always make a query, but it's very useful feature if you want to search one
|
||||
* item by using arbitrary set of matching fields. If there are more than one matching object, first one gets returned.
|
||||
*
|
||||
* @param null|int|string|array $keys An optional primary key value to load the object by, or an array of fields to match.
|
||||
* @param boolean $reload Force object to reload.
|
||||
*
|
||||
* @return Object
|
||||
*/
|
||||
static public function instance($keys = null, $reload = false);
|
||||
|
||||
/**
|
||||
* @param array $ids List of primary Ids or null to return everything that has been loaded.
|
||||
* @param bool $readonly
|
||||
* @return AbstractObjectCollection
|
||||
*/
|
||||
static public function instances(array $ids = null, $readonly = true);
|
||||
|
||||
/**
|
||||
* Removes all or selected instances from the object cache.
|
||||
*
|
||||
* @param null|int|string|array $ids An optional primary key or list of keys.
|
||||
*/
|
||||
static public function freeInstances($ids = null);
|
||||
|
||||
/**
|
||||
* Override this function if you need to initialize object right after creating it.
|
||||
*
|
||||
* Can be used for example if the fields need to be converted from json strings to array.
|
||||
*
|
||||
* @return bool True if initialization was done, false if object was already initialized.
|
||||
*/
|
||||
public function initialize();
|
||||
|
||||
/**
|
||||
* Method to perform sanity checks on the instance properties to ensure they are safe to store in the storage.
|
||||
*
|
||||
* Child classes should override this method to make sure the data they are storing in the storage is safe and as
|
||||
* expected before saving the object.
|
||||
*
|
||||
* @return boolean True if the instance is sane and able to be stored in the storage.
|
||||
*/
|
||||
public function check();
|
||||
}
|
||||
385
system/src/Grav/Framework/Object/ObjectStorageTrait.php
Normal file
385
system/src/Grav/Framework/Object/ObjectStorageTrait.php
Normal file
@@ -0,0 +1,385 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
use Grav\Framework\Object\Storage\StorageInterface;
|
||||
|
||||
/**
|
||||
* Trait for implementing stored objects.
|
||||
*
|
||||
* @property string $id
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
trait ObjectStorageTrait
|
||||
{
|
||||
/**
|
||||
* If you don't have global instance ids, override this in extending class.
|
||||
* @var array
|
||||
*/
|
||||
static protected $instances = [];
|
||||
|
||||
/**
|
||||
* If you don't have global storage, override this in extending class.
|
||||
* @var StorageInterface
|
||||
*/
|
||||
static protected $storage;
|
||||
|
||||
/**
|
||||
* Readonly object.
|
||||
* @var bool
|
||||
*/
|
||||
protected $readonly = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $initialized = false;
|
||||
|
||||
|
||||
/**
|
||||
* @param StorageInterface $storage
|
||||
*/
|
||||
static public function setStorage(StorageInterface $storage)
|
||||
{
|
||||
static::$storage = $storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the global instance to the object.
|
||||
*
|
||||
* Note that using array of fields will always make a query, but it's very useful feature if you want to search one
|
||||
* item by using arbitrary set of matching fields. If there are more than one matching object, first one gets returned.
|
||||
*
|
||||
* @param string|array $keys An optional primary key value to load the object by, or an array of fields to match.
|
||||
* @param boolean $reload Force object to reload.
|
||||
*
|
||||
* @return ObjectInterface
|
||||
*/
|
||||
static public function instance($keys = null, $reload = false)
|
||||
{
|
||||
if (is_scalar($keys)) {
|
||||
$keys = ['id' => (string) $keys];
|
||||
}
|
||||
$id = $keys ? static::getInstanceId($keys) : null;
|
||||
|
||||
// If we are creating or loading a new item or we load instance by alternative keys, we need to create a new object.
|
||||
if (!$id || !isset(static::$instances[$id])) {
|
||||
$c = get_called_class();
|
||||
|
||||
/** @var ObjectStorageTrait|ObjectInterface $instance */
|
||||
$instance = new $c();
|
||||
if (!$instance->load($keys)) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
// Instance exists in storage: make sure that we return the global instance.
|
||||
$id = $instance->getId();
|
||||
$reload = false;
|
||||
}
|
||||
|
||||
// Return global instance from the identifier.
|
||||
$instance = static::$instances[$id];
|
||||
|
||||
if ($reload) {
|
||||
$instance->load();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all or selected instances from the object cache.
|
||||
*
|
||||
* @param null|string|array $ids An optional primary key or list of keys.
|
||||
*/
|
||||
static public function freeInstances($ids = null)
|
||||
{
|
||||
if ($ids === null) {
|
||||
$ids = array_keys(static::$instances);
|
||||
}
|
||||
$ids = (array) $ids;
|
||||
|
||||
foreach ($ids as $id) {
|
||||
unset(static::$instances[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this function if you need to initialize object right after creating it.
|
||||
*
|
||||
* Can be used for example if the fields need to be converted from json strings to array.
|
||||
*
|
||||
* @return bool True if initialization was done, false if object was already initialized.
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
$initialized = $this->initialized;
|
||||
$this->initialized = true;
|
||||
|
||||
return !$initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert instance to a read only object.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function readonly()
|
||||
{
|
||||
$this->readonly = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the object has been stored.
|
||||
*
|
||||
* @return boolean True if object exists in storage.
|
||||
*/
|
||||
public function isSaved()
|
||||
{
|
||||
return $this->getStorage()->exists($this->getStorageKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to load object from the storage.
|
||||
*
|
||||
* @param mixed $keys An optional primary key value to load the object by, or an array of fields to match. If not
|
||||
* set the instance key value is used.
|
||||
* @param bool $getKey Internal parameter, please do not use.
|
||||
*
|
||||
* @return bool True on success, false if the object doesn't exist.
|
||||
*/
|
||||
public function load($keys = null, $getKey = true)
|
||||
{
|
||||
if ($getKey) {
|
||||
if (is_scalar($keys)) {
|
||||
$keys = ['id' => (string) $keys];
|
||||
}
|
||||
|
||||
// Fetch internal key.
|
||||
$key = $keys ? $this->getStorageKey($keys) : null;
|
||||
|
||||
} else {
|
||||
// Internal key was passed.
|
||||
$key = $keys;
|
||||
$keys = [];
|
||||
}
|
||||
|
||||
$this->doLoad($this->getStorage()->load($key), $keys);
|
||||
$this->initialize();
|
||||
|
||||
$id = $this->getId();
|
||||
if ($id) {
|
||||
if (!isset(static::$instances[$id])) {
|
||||
static::$instances[$id] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->isSaved();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to save the object to the storage.
|
||||
*
|
||||
* Before saving the object, this method checks if object can be safely saved.
|
||||
*
|
||||
* @param bool $includeChildren
|
||||
* @return bool True on success.
|
||||
*/
|
||||
public function save($includeChildren = false)
|
||||
{
|
||||
// Check the object.
|
||||
if ($this->readonly || !$this->check($includeChildren) || !$this->onBeforeSave()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get storage.
|
||||
$storage = $this->getStorage();
|
||||
$key = $this->getStorageKey();
|
||||
|
||||
// Get data to be saved.
|
||||
$data = $this->prepareSave();
|
||||
|
||||
// Save the object.
|
||||
$exists = $storage->exists($key);
|
||||
$id = $storage->save($key, $data);
|
||||
|
||||
if (!$id) {
|
||||
throw new \LogicException('No id specified');
|
||||
}
|
||||
|
||||
// If item was created, load the object (making sure it has been properly initialized).
|
||||
if (!$exists) {
|
||||
$this->load($id, false);
|
||||
}
|
||||
|
||||
if ($includeChildren) {
|
||||
$this->saveChildren();
|
||||
}
|
||||
|
||||
$this->onAfterSave();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to delete the object from the database.
|
||||
*
|
||||
* @param bool $includeChildren
|
||||
* @return bool True on success.
|
||||
*/
|
||||
public function delete($includeChildren = false)
|
||||
{
|
||||
if ($this->readonly || !$this->isSaved() || !$this->onBeforeDelete()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($includeChildren) {
|
||||
$this->deleteChildren();
|
||||
}
|
||||
|
||||
// Get storage.
|
||||
$storage = $this->getStorage();
|
||||
|
||||
if (!$storage->delete($this->getStorageKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->onAfterDelete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to perform sanity checks on the instance properties to ensure they are safe to store in the storage.
|
||||
*
|
||||
* Child classes should override this method to make sure the data they are storing in the storage is safe and as
|
||||
* expected before saving the object.
|
||||
*
|
||||
* @return bool True if the instance is sane and able to be stored in the storage.
|
||||
*/
|
||||
public function check($includeChildren = false)
|
||||
{
|
||||
$result = true;
|
||||
|
||||
if ($includeChildren) {
|
||||
foreach ($this->toArray() as $field => $value) {
|
||||
if (is_object($value) && method_exists($value, 'check')) {
|
||||
$result = $result && $value->check();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Internal functions
|
||||
|
||||
abstract protected function doLoad(array $items, array $keys = []);
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function onBeforeSave()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function onAfterSave()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected function onBeforeDelete()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function onAfterDelete()
|
||||
{
|
||||
}
|
||||
|
||||
protected function saveChildren()
|
||||
{
|
||||
foreach ($this->toArray() as $field => $value) {
|
||||
if (is_object($value) && method_exists($value, 'save')) {
|
||||
$value->save(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function deleteChildren()
|
||||
{
|
||||
foreach ($this->toArray() as $field => $value) {
|
||||
if (is_object($value) && method_exists($value, 'delete')) {
|
||||
$value->delete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function prepareSave(array $data = null)
|
||||
{
|
||||
if ($data === null) {
|
||||
$data = $this->toArray();
|
||||
}
|
||||
|
||||
foreach ($data as $field => $value) {
|
||||
if (is_object($value) && method_exists($value, 'save')) {
|
||||
unset($data[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the storage key for the object.
|
||||
*
|
||||
* @param array
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getStorageKey(array $keys = []);
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @return string
|
||||
*/
|
||||
public function getInstanceId(array $keys)
|
||||
{
|
||||
return $this->getStorageKey($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->getStorageKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StorageInterface
|
||||
*/
|
||||
protected static function getStorage()
|
||||
{
|
||||
if (!static::$storage) {
|
||||
static::loadStorage();
|
||||
}
|
||||
|
||||
return static::$storage;
|
||||
}
|
||||
|
||||
protected static function loadStorage()
|
||||
{
|
||||
throw new \RuntimeException('Storage has not been set.');
|
||||
}
|
||||
}
|
||||
145
system/src/Grav/Framework/Object/Storage/FilesystemStorage.php
Normal file
145
system/src/Grav/Framework/Object/Storage/FilesystemStorage.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object\Storage
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Storage;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use RocketTheme\Toolbox\File\FileInterface;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
|
||||
/**
|
||||
* FilesystemStorage
|
||||
* @package Grav\Framework\Object\Storage
|
||||
*/
|
||||
class FilesystemStorage implements StorageInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Grav\\Common\\File\\CompiledJsonFile';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extension = '.json';
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $extension
|
||||
* @param string $type
|
||||
*/
|
||||
public function __construct($path, $type = null, $extension = null)
|
||||
{
|
||||
$this->path = $path;
|
||||
if ($type) {
|
||||
$this->type = $type;
|
||||
}
|
||||
if ($extension) {
|
||||
$this->extension = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($key)
|
||||
{
|
||||
if ($key === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = $this->getFile($key);
|
||||
|
||||
return $file->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
public function load($key)
|
||||
{
|
||||
if ($key === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$file = $this->getFile($key);
|
||||
$content = (array)$file->content();
|
||||
$file->free();
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function save($key, array $data)
|
||||
{
|
||||
$file = $this->getFile($key);
|
||||
$file->save($data);
|
||||
$file->free();
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
$file = $this->getFile($key);
|
||||
$result = $file->delete();
|
||||
$file->free();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $list
|
||||
* @return array
|
||||
*/
|
||||
public function loadList(array $list)
|
||||
{
|
||||
$results = [];
|
||||
foreach ($list as $id) {
|
||||
$results[$id] = $this->load($id);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return FileInterface
|
||||
*/
|
||||
protected function getFile($key)
|
||||
{
|
||||
if ($key === null) {
|
||||
throw new \RuntimeException('Storage key not defined');
|
||||
}
|
||||
|
||||
$filename = "{$this->path}/{$key}{$this->extension}";
|
||||
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = Grav::instance()['locator'];
|
||||
|
||||
/** @var FileInterface $type */
|
||||
$type = $this->type;
|
||||
|
||||
return $type::instance($locator->findResource($filename, true) ?: $locator->findResource($filename, true, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object\Storage
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object\Storage;
|
||||
|
||||
/**
|
||||
* Interface StorageInterface
|
||||
* @package Grav\Framework\Object\Storage
|
||||
*/
|
||||
interface StorageInterface
|
||||
{
|
||||
/**
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($key);
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
public function load($key);
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function save($key, array $data);
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function delete($key);
|
||||
|
||||
/**
|
||||
* @param string[] $list
|
||||
* @return array
|
||||
*/
|
||||
public function loadList(array $list);
|
||||
}
|
||||
58
system/src/Grav/Framework/Object/StoredObjectInterface.php
Normal file
58
system/src/Grav/Framework/Object/StoredObjectInterface.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav\Framework\Object
|
||||
*
|
||||
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Framework\Object;
|
||||
|
||||
/**
|
||||
* Stored Object Interface
|
||||
* @package Grav\Framework\Object
|
||||
*/
|
||||
interface StoredObjectInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Convert instance to a read only object.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function readonly();
|
||||
|
||||
/**
|
||||
* Returns true if the object exists in the storage.
|
||||
*
|
||||
* @return boolean True if object exists.
|
||||
*/
|
||||
public function isSaved();
|
||||
|
||||
/**
|
||||
* Method to load object from the storage.
|
||||
*
|
||||
* @param null|int|string|array $keys An optional primary key value to load the object by, or an array of fields
|
||||
* to match. If not set the instance key value is used.
|
||||
*
|
||||
* @return boolean True on success, false if the object doesn't exist.
|
||||
*/
|
||||
public function load($keys = null);
|
||||
|
||||
/**
|
||||
* Method to save the object to the storage.
|
||||
*
|
||||
* Before saving the object, this method checks if object can be safely saved.
|
||||
*
|
||||
* @param bool $includeChildren
|
||||
* @return boolean True on success.
|
||||
*/
|
||||
public function save($includeChildren = false);
|
||||
|
||||
/**
|
||||
* Method to delete the object from the database.
|
||||
*
|
||||
* @return boolean True on success.
|
||||
*/
|
||||
public function delete();
|
||||
}
|
||||
Reference in New Issue
Block a user