This commit is contained in:
2019-09-27 00:29:18 +02:00
parent 40af43681e
commit b857814072
470 changed files with 37562 additions and 10060 deletions
+216 -142
View File
@@ -1,21 +1,48 @@
<?php
/**
* @package Grav.Common
* @package Grav\Common
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common;
use Grav\Common\Config\Config;
use Grav\Common\Config\Setup;
use Grav\Common\Page\Interfaces\PageInterface;
use Grav\Common\Page\Medium\ImageMedium;
use Grav\Common\Page\Medium\Medium;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\DI\Container;
use Grav\Common\Processors\AssetsProcessor;
use Grav\Common\Processors\BackupsProcessor;
use Grav\Common\Processors\ConfigurationProcessor;
use Grav\Common\Processors\DebuggerAssetsProcessor;
use Grav\Common\Processors\DebuggerProcessor;
use Grav\Common\Processors\ErrorsProcessor;
use Grav\Common\Processors\InitializeProcessor;
use Grav\Common\Processors\LoggerProcessor;
use Grav\Common\Processors\PagesProcessor;
use Grav\Common\Processors\PluginsProcessor;
use Grav\Common\Processors\RenderProcessor;
use Grav\Common\Processors\RequestProcessor;
use Grav\Common\Processors\SchedulerProcessor;
use Grav\Common\Processors\TasksProcessor;
use Grav\Common\Processors\ThemesProcessor;
use Grav\Common\Processors\TwigProcessor;
use Grav\Framework\DI\Container;
use Grav\Framework\Psr7\Response;
use Grav\Framework\RequestHandler\RequestHandler;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;
/**
* Grav container is the heart of Grav.
*
* @package Grav\Common
*/
class Grav extends Container
{
/**
@@ -33,54 +60,47 @@ class Grav extends Container
* to the dependency injection container.
*/
protected static $diMap = [
'Grav\Common\Service\LoggerServiceProvider',
'Grav\Common\Service\ErrorServiceProvider',
'uri' => 'Grav\Common\Uri',
'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
'cache' => 'Grav\Common\Cache',
'Grav\Common\Service\SessionServiceProvider',
'plugins' => 'Grav\Common\Plugins',
'themes' => 'Grav\Common\Themes',
'twig' => 'Grav\Common\Twig\Twig',
'taxonomy' => 'Grav\Common\Taxonomy',
'language' => 'Grav\Common\Language\Language',
'pages' => 'Grav\Common\Page\Pages',
'Grav\Common\Service\TaskServiceProvider',
'Grav\Common\Service\AccountsServiceProvider',
'Grav\Common\Service\AssetsServiceProvider',
'Grav\Common\Service\PageServiceProvider',
'Grav\Common\Service\OutputServiceProvider',
'browser' => 'Grav\Common\Browser',
'exif' => 'Grav\Common\Helpers\Exif',
'Grav\Common\Service\StreamsServiceProvider',
'Grav\Common\Service\BackupsServiceProvider',
'Grav\Common\Service\ConfigServiceProvider',
'inflector' => 'Grav\Common\Inflector',
'siteSetupProcessor' => 'Grav\Common\Processors\SiteSetupProcessor',
'configurationProcessor' => 'Grav\Common\Processors\ConfigurationProcessor',
'errorsProcessor' => 'Grav\Common\Processors\ErrorsProcessor',
'debuggerInitProcessor' => 'Grav\Common\Processors\DebuggerInitProcessor',
'initializeProcessor' => 'Grav\Common\Processors\InitializeProcessor',
'pluginsProcessor' => 'Grav\Common\Processors\PluginsProcessor',
'themesProcessor' => 'Grav\Common\Processors\ThemesProcessor',
'tasksProcessor' => 'Grav\Common\Processors\TasksProcessor',
'assetsProcessor' => 'Grav\Common\Processors\AssetsProcessor',
'twigProcessor' => 'Grav\Common\Processors\TwigProcessor',
'pagesProcessor' => 'Grav\Common\Processors\PagesProcessor',
'debuggerAssetsProcessor' => 'Grav\Common\Processors\DebuggerAssetsProcessor',
'renderProcessor' => 'Grav\Common\Processors\RenderProcessor',
'Grav\Common\Service\ErrorServiceProvider',
'Grav\Common\Service\FilesystemServiceProvider',
'Grav\Common\Service\InflectorServiceProvider',
'Grav\Common\Service\LoggerServiceProvider',
'Grav\Common\Service\OutputServiceProvider',
'Grav\Common\Service\PagesServiceProvider',
'Grav\Common\Service\RequestServiceProvider',
'Grav\Common\Service\SessionServiceProvider',
'Grav\Common\Service\StreamsServiceProvider',
'Grav\Common\Service\TaskServiceProvider',
'browser' => 'Grav\Common\Browser',
'cache' => 'Grav\Common\Cache',
'events' => 'RocketTheme\Toolbox\Event\EventDispatcher',
'exif' => 'Grav\Common\Helpers\Exif',
'plugins' => 'Grav\Common\Plugins',
'scheduler' => 'Grav\Common\Scheduler\Scheduler',
'taxonomy' => 'Grav\Common\Taxonomy',
'themes' => 'Grav\Common\Themes',
'twig' => 'Grav\Common\Twig\Twig',
'uri' => 'Grav\Common\Uri',
];
/**
* @var array All processors that are processed in $this->process()
* @var array All middleware processors that are processed in $this->process()
*/
protected $processors = [
'siteSetupProcessor',
protected $middleware = [
'configurationProcessor',
'loggerProcessor',
'errorsProcessor',
'debuggerInitProcessor',
'debuggerProcessor',
'initializeProcessor',
'pluginsProcessor',
'themesProcessor',
'requestProcessor',
'tasksProcessor',
'backupsProcessor',
'schedulerProcessor',
'assetsProcessor',
'twigProcessor',
'pagesProcessor',
@@ -88,6 +108,8 @@ class Grav extends Container
'renderProcessor',
];
protected $initialized = [];
/**
* Reset the Grav instance.
*/
@@ -119,21 +141,116 @@ class Grav extends Container
return self::$instance;
}
/**
* Setup Grav instance using specific environment.
*
* Initializes Grav streams by
*
* @param string|null $environment
* @return $this
*/
public function setup(string $environment = null)
{
if (isset($this->initialized['setup'])) {
return $this;
}
$this->initialized['setup'] = true;
$this->measureTime('_setup', 'Site Setup', function () use ($environment) {
// Force environment if passed to the method.
if ($environment) {
Setup::$environment = $environment;
}
$this['setup'];
$this['streams'];
});
return $this;
}
/**
* Process a request
*/
public function process()
{
// process all processors (e.g. config, initialize, assets, ..., render)
foreach ($this->processors as $processor) {
$processor = $this[$processor];
$this->measureTime($processor->id, $processor->title, function () use ($processor) {
$processor->process();
});
if (isset($this->initialized['process'])) {
return;
}
// Initialize Grav if needed.
$this->setup();
$this->initialized['process'] = true;
$container = new Container(
[
'configurationProcessor' => function () {
return new ConfigurationProcessor($this);
},
'loggerProcessor' => function () {
return new LoggerProcessor($this);
},
'errorsProcessor' => function () {
return new ErrorsProcessor($this);
},
'debuggerProcessor' => function () {
return new DebuggerProcessor($this);
},
'initializeProcessor' => function () {
return new InitializeProcessor($this);
},
'backupsProcessor' => function () {
return new BackupsProcessor($this);
},
'pluginsProcessor' => function () {
return new PluginsProcessor($this);
},
'themesProcessor' => function () {
return new ThemesProcessor($this);
},
'schedulerProcessor' => function () {
return new SchedulerProcessor($this);
},
'requestProcessor' => function () {
return new RequestProcessor($this);
},
'tasksProcessor' => function () {
return new TasksProcessor($this);
},
'assetsProcessor' => function () {
return new AssetsProcessor($this);
},
'twigProcessor' => function () {
return new TwigProcessor($this);
},
'pagesProcessor' => function () {
return new PagesProcessor($this);
},
'debuggerAssetsProcessor' => function () {
return new DebuggerAssetsProcessor($this);
},
'renderProcessor' => function () {
return new RenderProcessor($this);
},
]
);
$default = function (ServerRequestInterface $request) {
return new Response(404);
};
/** @var Debugger $debugger */
$debugger = $this['debugger'];
$collection = new RequestHandler($this->middleware, $default, $container);
$response = $collection->handle($this['request']);
$this->header($response);
echo $response->getBody();
$debugger->render();
register_shutdown_function([$this, 'shutdown']);
@@ -147,7 +264,7 @@ class Grav extends Container
// Initialize Locale if set and configured.
if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
$language = $this['language']->getLanguage();
setlocale(LC_ALL, strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
} elseif ($this['config']->get('system.default_locale')) {
setlocale(LC_ALL, $this['config']->get('system.default_locale'));
}
@@ -213,53 +330,22 @@ class Grav extends Container
/**
* Set response header.
*
* @param ResponseInterface|null $response
*/
public function header()
public function header(ResponseInterface $response = null)
{
/** @var Page $page */
$page = $this['page'];
if (null === $response) {
/** @var PageInterface $page */
$page = $this['page'];
$response = new Response($page->httpResponseCode(), $page->httpHeaders(), '');
}
$format = $page->templateFormat();
header('Content-type: ' . Utils::getMimeByExtension($format, 'text/html'));
$cache_control = $page->cacheControl();
// Calculate Expires Headers if set to > 0
$expires = $page->expires();
if ($expires > 0) {
$expires_date = gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT';
if (!$cache_control) {
header('Cache-Control: max-age=' . $expires);
header("HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}");
foreach ($response->getHeaders() as $key => $values) {
foreach ($values as $i => $value) {
header($key . ': ' . $value, $i === 0);
}
header('Expires: ' . $expires_date);
}
// Set cache-control header
if ($cache_control) {
header('Cache-Control: ' . strtolower($cache_control));
}
// Set the last modified time
if ($page->lastModified()) {
$last_modified_date = gmdate('D, d M Y H:i:s', $page->modified()) . ' GMT';
header('Last-Modified: ' . $last_modified_date);
}
// Calculate a Hash based on the raw file
if ($page->eTag()) {
header('ETag: "' . md5($page->raw() . $page->modified()).'"');
}
// Set HTTP response code
if (isset($this['page']->header()->http_response_code)) {
http_response_code($this['page']->header()->http_response_code);
}
// Vary: Accept-Encoding
if ($this['config']->get('system.pages.vary_accept_encoding', false)) {
header('Vary: Accept-Encoding');
}
}
@@ -286,7 +372,7 @@ class Grav extends Container
public function shutdown()
{
// Prevent user abort allowing onShutdown event to run without interruptions.
if (function_exists('ignore_user_abort')) {
if (\function_exists('ignore_user_abort')) {
@ignore_user_abort(true);
}
@@ -300,7 +386,7 @@ class Grav extends Container
// the connection to the client open. This will make page loads to feel much faster.
// FastCGI allows us to flush all response data to the client and finish the request.
$success = function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
$success = \function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
if (!$success) {
// Unfortunately without FastCGI there is no way to force close the connection.
@@ -323,7 +409,7 @@ class Grav extends Container
// Get length and close the connection.
header('Content-Length: ' . ob_get_length());
header("Connection: close");
header('Connection: close');
ob_end_flush();
@ob_flush();
@@ -337,13 +423,33 @@ class Grav extends Container
/**
* Magic Catch All Function
* Used to call closures like measureTime on the instance.
*
* Used to call closures.
*
* Source: http://stackoverflow.com/questions/419804/closures-as-class-members
*/
public function __call($method, $args)
{
$closure = $this->$method;
call_user_func_array($closure, $args);
$closure = $this->{$method};
\call_user_func_array($closure, $args);
}
/**
* Measure how long it takes to do an action.
*
* @param string $timerId
* @param string $timerTitle
* @param callable $callback
* @return mixed Returns value returned by the callable.
*/
public function measureTime(string $timerId, string $timerTitle, callable $callback)
{
$debugger = $this['debugger'];
$debugger->startTimer($timerId, $timerTitle);
$result = $callback();
$debugger->stopTimer($timerId);
return $result;
}
/**
@@ -357,22 +463,15 @@ class Grav extends Container
{
$container = new static($values);
$container['grav'] = $container;
$container['debugger'] = new Debugger();
$debugger = $container['debugger'];
$container['grav'] = function (Container $container) {
user_error('Calling $grav[\'grav\'] or {{ grav.grav }} is deprecated since Grav 1.6, just use $grav or {{ grav }}', E_USER_DEPRECATED);
// closure that measures time by wrapping a function into startTimer and stopTimer
// The debugger can be passed to the closure. Should be more performant
// then to get it from the container all time.
$container->measureTime = function ($timerId, $timerTitle, $callback) use ($debugger) {
$debugger->startTimer($timerId, $timerTitle);
$callback();
$debugger->stopTimer($timerId);
return $container;
};
$container->measureTime('_services', 'Services', function () use ($container) {
$container->registerServices($container);
$container->registerServices();
});
return $container;
@@ -389,45 +488,20 @@ class Grav extends Container
protected function registerServices()
{
foreach (self::$diMap as $serviceKey => $serviceClass) {
if (is_int($serviceKey)) {
$this->registerServiceProvider($serviceClass);
if (\is_int($serviceKey)) {
$this->register(new $serviceClass);
} else {
$this->registerService($serviceKey, $serviceClass);
$this[$serviceKey] = function ($c) use ($serviceClass) {
return new $serviceClass($c);
};
}
}
}
/**
* Register a service provider with the container.
*
* @param string $serviceClass
*
* @return void
*/
protected function registerServiceProvider($serviceClass)
{
$this->register(new $serviceClass);
}
/**
* Register a service with the container.
*
* @param string $serviceKey
* @param string $serviceClass
*
* @return void
*/
protected function registerService($serviceKey, $serviceClass)
{
$this[$serviceKey] = function ($c) use ($serviceClass) {
return new $serviceClass($c);
};
}
/**
* This attempts to find media, other files, and download them
*
* @param $path
* @param string $path
*/
public function fallbackUrl($path)
{
@@ -453,7 +527,7 @@ class Grav extends Container
$path_parts = pathinfo($path);
/** @var Page $page */
/** @var PageInterface $page */
$page = $this['pages']->dispatch($path_parts['dirname'], true);
if ($page) {
@@ -466,8 +540,8 @@ class Grav extends Container
/** @var Medium $medium */
$medium = $media[$media_file];
foreach ($uri->query(null, true) as $action => $params) {
if (in_array($action, ImageMedium::$magic_actions)) {
call_user_func_array([&$medium, $action], explode(',', $params));
if (\in_array($action, ImageMedium::$magic_actions, true)) {
\call_user_func_array([&$medium, $action], explode(',', $params));
}
}
Utils::download($medium->path(), false);
@@ -486,7 +560,7 @@ class Grav extends Container
if ($extension) {
$download = true;
if (in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []))) {
if (\in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []), true)) {
$download = false;
}
Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);