"Fossies" - the Fresh Open Source Software Archive

Member "grav/system/src/Grav/Common/Grav.php" (1 Sep 2020, 19629 Bytes) of package /linux/www/grav-v1.6.27.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Grav.php" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: v1.6.22_vs_v1.6.23.

    1 <?php
    2 
    3 /**
    4  * @package    Grav\Common
    5  *
    6  * @copyright  Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
    7  * @license    MIT License; see LICENSE file for details.
    8  */
    9 
   10 namespace Grav\Common;
   11 
   12 use Grav\Common\Config\Config;
   13 use Grav\Common\Config\Setup;
   14 use Grav\Common\Page\Interfaces\PageInterface;
   15 use Grav\Common\Page\Medium\ImageMedium;
   16 use Grav\Common\Page\Medium\Medium;
   17 use Grav\Common\Processors\AssetsProcessor;
   18 use Grav\Common\Processors\BackupsProcessor;
   19 use Grav\Common\Processors\ConfigurationProcessor;
   20 use Grav\Common\Processors\DebuggerAssetsProcessor;
   21 use Grav\Common\Processors\DebuggerProcessor;
   22 use Grav\Common\Processors\ErrorsProcessor;
   23 use Grav\Common\Processors\InitializeProcessor;
   24 use Grav\Common\Processors\LoggerProcessor;
   25 use Grav\Common\Processors\PagesProcessor;
   26 use Grav\Common\Processors\PluginsProcessor;
   27 use Grav\Common\Processors\RenderProcessor;
   28 use Grav\Common\Processors\RequestProcessor;
   29 use Grav\Common\Processors\SchedulerProcessor;
   30 use Grav\Common\Processors\TasksProcessor;
   31 use Grav\Common\Processors\ThemesProcessor;
   32 use Grav\Common\Processors\TwigProcessor;
   33 use Grav\Framework\DI\Container;
   34 use Grav\Framework\Psr7\Response;
   35 use Grav\Framework\RequestHandler\RequestHandler;
   36 use Psr\Http\Message\ResponseInterface;
   37 use Psr\Http\Message\ServerRequestInterface;
   38 use RocketTheme\Toolbox\Event\Event;
   39 use RocketTheme\Toolbox\Event\EventDispatcher;
   40 
   41 /**
   42  * Grav container is the heart of Grav.
   43  *
   44  * @package Grav\Common
   45  */
   46 class Grav extends Container
   47 {
   48     /**
   49      * @var string Processed output for the page.
   50      */
   51     public $output;
   52 
   53     /**
   54      * @var static The singleton instance
   55      */
   56     protected static $instance;
   57 
   58     /**
   59      * @var array Contains all Services and ServicesProviders that are mapped
   60      *            to the dependency injection container.
   61      */
   62     protected static $diMap = [
   63         'Grav\Common\Service\AccountsServiceProvider',
   64         'Grav\Common\Service\AssetsServiceProvider',
   65         'Grav\Common\Service\BackupsServiceProvider',
   66         'Grav\Common\Service\ConfigServiceProvider',
   67         'Grav\Common\Service\ErrorServiceProvider',
   68         'Grav\Common\Service\FilesystemServiceProvider',
   69         'Grav\Common\Service\InflectorServiceProvider',
   70         'Grav\Common\Service\LoggerServiceProvider',
   71         'Grav\Common\Service\OutputServiceProvider',
   72         'Grav\Common\Service\PagesServiceProvider',
   73         'Grav\Common\Service\RequestServiceProvider',
   74         'Grav\Common\Service\SessionServiceProvider',
   75         'Grav\Common\Service\StreamsServiceProvider',
   76         'Grav\Common\Service\TaskServiceProvider',
   77         'browser'    => 'Grav\Common\Browser',
   78         'cache'      => 'Grav\Common\Cache',
   79         'events'     => 'RocketTheme\Toolbox\Event\EventDispatcher',
   80         'exif'       => 'Grav\Common\Helpers\Exif',
   81         'plugins'    => 'Grav\Common\Plugins',
   82         'scheduler'  => 'Grav\Common\Scheduler\Scheduler',
   83         'taxonomy'   => 'Grav\Common\Taxonomy',
   84         'themes'     => 'Grav\Common\Themes',
   85         'twig'       => 'Grav\Common\Twig\Twig',
   86         'uri'        => 'Grav\Common\Uri',
   87     ];
   88 
   89     /**
   90      * @var array All middleware processors that are processed in $this->process()
   91      */
   92     protected $middleware = [
   93         'configurationProcessor',
   94         'loggerProcessor',
   95         'errorsProcessor',
   96         'debuggerProcessor',
   97         'initializeProcessor',
   98         'pluginsProcessor',
   99         'themesProcessor',
  100         'requestProcessor',
  101         'tasksProcessor',
  102         'backupsProcessor',
  103         'schedulerProcessor',
  104         'assetsProcessor',
  105         'twigProcessor',
  106         'pagesProcessor',
  107         'debuggerAssetsProcessor',
  108         'renderProcessor',
  109     ];
  110 
  111     protected $initialized = [];
  112 
  113     /**
  114      * Reset the Grav instance.
  115      */
  116     public static function resetInstance()
  117     {
  118         if (self::$instance) {
  119             self::$instance = null;
  120         }
  121     }
  122 
  123     /**
  124      * Return the Grav instance. Create it if it's not already instanced
  125      *
  126      * @param array $values
  127      *
  128      * @return Grav
  129      */
  130     public static function instance(array $values = [])
  131     {
  132         if (!self::$instance) {
  133             self::$instance = static::load($values);
  134         } elseif ($values) {
  135             $instance = self::$instance;
  136             foreach ($values as $key => $value) {
  137                 $instance->offsetSet($key, $value);
  138             }
  139         }
  140 
  141         return self::$instance;
  142     }
  143 
  144     /**
  145      * Setup Grav instance using specific environment.
  146      *
  147      * Initializes Grav streams by
  148      *
  149      * @param string|null $environment
  150      * @return $this
  151      */
  152     public function setup(string $environment = null)
  153     {
  154         if (isset($this->initialized['setup'])) {
  155             return $this;
  156         }
  157 
  158         $this->initialized['setup'] = true;
  159 
  160         $this->measureTime('_setup', 'Site Setup', function () use ($environment) {
  161             // Force environment if passed to the method.
  162             if ($environment) {
  163                 Setup::$environment = $environment;
  164             }
  165 
  166             $this['setup'];
  167             $this['streams'];
  168         });
  169 
  170         return $this;
  171     }
  172 
  173     /**
  174      * Initialize CLI environment.
  175      *
  176      * Call after `$grav->setup($environment)`
  177      *
  178      * - Load configuration
  179      * - Disable debugger
  180      * - Set timezone, locale
  181      * - Load plugins
  182      * - Set Users type to be used in the site
  183      *
  184      * This method WILL NOT initialize assets, twig or pages.
  185      *
  186      * @param string|null $environment
  187      * @return $this
  188      */
  189     public function initializeCli()
  190     {
  191         InitializeProcessor::initializeCli($this);
  192 
  193         return $this;
  194     }
  195 
  196     /**
  197      * Process a request
  198      */
  199     public function process()
  200     {
  201         if (isset($this->initialized['process'])) {
  202             return;
  203         }
  204 
  205         // Initialize Grav if needed.
  206         $this->setup();
  207 
  208         $this->initialized['process'] = true;
  209 
  210         $container = new Container(
  211             [
  212                 'configurationProcessor' => function () {
  213                     return new ConfigurationProcessor($this);
  214                 },
  215                 'loggerProcessor' => function () {
  216                     return new LoggerProcessor($this);
  217                 },
  218                 'errorsProcessor' => function () {
  219                     return new ErrorsProcessor($this);
  220                 },
  221                 'debuggerProcessor' => function () {
  222                     return new DebuggerProcessor($this);
  223                 },
  224                 'initializeProcessor' => function () {
  225                     return new InitializeProcessor($this);
  226                 },
  227                 'backupsProcessor' => function () {
  228                     return new BackupsProcessor($this);
  229                 },
  230                 'pluginsProcessor' => function () {
  231                     return new PluginsProcessor($this);
  232                 },
  233                 'themesProcessor' => function () {
  234                     return new ThemesProcessor($this);
  235                 },
  236                 'schedulerProcessor' => function () {
  237                     return new SchedulerProcessor($this);
  238                 },
  239                 'requestProcessor' => function () {
  240                     return new RequestProcessor($this);
  241                 },
  242                 'tasksProcessor' => function () {
  243                     return new TasksProcessor($this);
  244                 },
  245                 'assetsProcessor' => function () {
  246                     return new AssetsProcessor($this);
  247                 },
  248                 'twigProcessor' => function () {
  249                     return new TwigProcessor($this);
  250                 },
  251                 'pagesProcessor' => function () {
  252                     return new PagesProcessor($this);
  253                 },
  254                 'debuggerAssetsProcessor' => function () {
  255                     return new DebuggerAssetsProcessor($this);
  256                 },
  257                 'renderProcessor' => function () {
  258                     return new RenderProcessor($this);
  259                 },
  260             ]
  261         );
  262 
  263         $default = function (ServerRequestInterface $request) {
  264             return new Response(404);
  265         };
  266 
  267         /** @var Debugger $debugger */
  268         $debugger = $this['debugger'];
  269 
  270         $collection = new RequestHandler($this->middleware, $default, $container);
  271 
  272         $response = $collection->handle($this['request']);
  273         $body = $response->getBody();
  274 
  275         // Handle ETag and If-None-Match headers.
  276         if ($response->getHeaderLine('ETag') === '1') {
  277             $etag = md5($body);
  278             $response = $response->withHeader('ETag', $etag);
  279 
  280             if ($this['request']->getHeaderLine('If-None-Match') === $etag) {
  281                 $response = $response->withStatus(304);
  282                 $body = '';
  283             }
  284         }
  285 
  286         $this->header($response);
  287         echo $body;
  288 
  289         $debugger->render();
  290 
  291         register_shutdown_function([$this, 'shutdown']);
  292     }
  293 
  294     /**
  295      * Set the system locale based on the language and configuration
  296      */
  297     public function setLocale()
  298     {
  299         // Initialize Locale if set and configured.
  300         if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
  301             $language = $this['language']->getLanguage();
  302             setlocale(LC_ALL, \strlen($language) < 3 ? ($language . '_' . strtoupper($language)) : $language);
  303         } elseif ($this['config']->get('system.default_locale')) {
  304             setlocale(LC_ALL, $this['config']->get('system.default_locale'));
  305         }
  306     }
  307 
  308     /**
  309      * Redirect browser to another location.
  310      *
  311      * @param string $route Internal route.
  312      * @param int    $code  Redirection code (30x)
  313      */
  314     public function redirect($route, $code = null)
  315     {
  316         /** @var Uri $uri */
  317         $uri = $this['uri'];
  318 
  319         // Clean route for redirect
  320         $route = preg_replace("#^\/[\\\/]+\/#", '/', $route);
  321 
  322          // Check for code in route
  323         $regex = '/.*(\[(30[1-7])\])$/';
  324         preg_match($regex, $route, $matches);
  325         if ($matches) {
  326             $route = str_replace($matches[1], '', $matches[0]);
  327             $code = $matches[2];
  328         }
  329 
  330         if ($code === null) {
  331             $code = $this['config']->get('system.pages.redirect_default_code', 302);
  332         }
  333 
  334         if (isset($this['session'])) {
  335             $this['session']->close();
  336         }
  337 
  338         if ($uri->isExternal($route)) {
  339             $url = $route;
  340         } else {
  341             $url = rtrim($uri->rootUrl(), '/') . '/';
  342 
  343             if ($this['config']->get('system.pages.redirect_trailing_slash', true)) {
  344                 $url .= trim($route, '/'); // Remove trailing slash
  345             } else {
  346                 $url .= ltrim($route, '/'); // Support trailing slash default routes
  347             }
  348         }
  349 
  350         header("Location: {$url}", true, $code);
  351         exit();
  352     }
  353 
  354     /**
  355      * Redirect browser to another location taking language into account (preferred)
  356      *
  357      * @param string $route Internal route.
  358      * @param int    $code  Redirection code (30x)
  359      */
  360     public function redirectLangSafe($route, $code = null)
  361     {
  362         if (!$this['uri']->isExternal($route)) {
  363             $this->redirect($this['pages']->route($route), $code);
  364         } else {
  365             $this->redirect($route, $code);
  366         }
  367     }
  368 
  369     /**
  370      * Set response header.
  371      *
  372      * @param ResponseInterface|null $response
  373      */
  374     public function header(ResponseInterface $response = null)
  375     {
  376         if (null === $response) {
  377             /** @var PageInterface $page */
  378             $page = $this['page'];
  379             $response = new Response($page->httpResponseCode(), $page->httpHeaders(), '');
  380         }
  381 
  382         header("HTTP/{$response->getProtocolVersion()} {$response->getStatusCode()} {$response->getReasonPhrase()}");
  383         foreach ($response->getHeaders() as $key => $values) {
  384             foreach ($values as $i => $value) {
  385                 header($key . ': ' . $value, $i === 0);
  386             }
  387         }
  388     }
  389 
  390     /**
  391      * Fires an event with optional parameters.
  392      *
  393      * @param  string $eventName
  394      * @param  Event  $event
  395      *
  396      * @return Event
  397      */
  398     public function fireEvent($eventName, Event $event = null)
  399     {
  400         /** @var EventDispatcher $events */
  401         $events = $this['events'];
  402 
  403         return $events->dispatch($eventName, $event);
  404     }
  405 
  406     /**
  407      * Set the final content length for the page and flush the buffer
  408      *
  409      */
  410     public function shutdown()
  411     {
  412         // Prevent user abort allowing onShutdown event to run without interruptions.
  413         if (\function_exists('ignore_user_abort')) {
  414             @ignore_user_abort(true);
  415         }
  416 
  417         // Close the session allowing new requests to be handled.
  418         if (isset($this['session'])) {
  419             $this['session']->close();
  420         }
  421 
  422         if ($this['config']->get('system.debugger.shutdown.close_connection', true)) {
  423             // Flush the response and close the connection to allow time consuming tasks to be performed without leaving
  424             // the connection to the client open. This will make page loads to feel much faster.
  425 
  426             // FastCGI allows us to flush all response data to the client and finish the request.
  427             $success = \function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
  428 
  429             if (!$success) {
  430                 // Unfortunately without FastCGI there is no way to force close the connection.
  431                 // We need to ask browser to close the connection for us.
  432                 if ($this['config']->get('system.cache.gzip')) {
  433                     // Flush gzhandler buffer if gzip setting was enabled.
  434                     ob_end_flush();
  435 
  436                 } else {
  437                     // Without gzip we have no other choice than to prevent server from compressing the output.
  438                     // This action turns off mod_deflate which would prevent us from closing the connection.
  439                     if ($this['config']->get('system.cache.allow_webserver_gzip')) {
  440                         header('Content-Encoding: identity');
  441                     } else {
  442                         header('Content-Encoding: none');
  443                     }
  444 
  445                 }
  446 
  447 
  448                 // Get length and close the connection.
  449                 header('Content-Length: ' . ob_get_length());
  450                 header('Connection: close');
  451 
  452                 ob_end_flush();
  453                 @ob_flush();
  454                 flush();
  455             }
  456         }
  457 
  458         // Run any time consuming tasks.
  459         $this->fireEvent('onShutdown');
  460     }
  461 
  462     /**
  463      * Magic Catch All Function
  464      *
  465      * Used to call closures.
  466      *
  467      * Source: http://stackoverflow.com/questions/419804/closures-as-class-members
  468      *
  469      * @param string $method
  470      * @param array $args
  471      * @return
  472      */
  473     public function __call($method, $args)
  474     {
  475         $closure = $this->{$method} ?? null;
  476 
  477         return is_callable($closure) ? $closure(...$args) : null;
  478     }
  479 
  480     /**
  481      * Measure how long it takes to do an action.
  482      *
  483      * @param string $timerId
  484      * @param string $timerTitle
  485      * @param callable $callback
  486      * @return mixed   Returns value returned by the callable.
  487      */
  488     public function measureTime(string $timerId, string $timerTitle, callable $callback)
  489     {
  490         $debugger = $this['debugger'];
  491         $debugger->startTimer($timerId, $timerTitle);
  492         $result = $callback();
  493         $debugger->stopTimer($timerId);
  494 
  495         return $result;
  496     }
  497 
  498     /**
  499      * Initialize and return a Grav instance
  500      *
  501      * @param  array $values
  502      *
  503      * @return static
  504      */
  505     protected static function load(array $values)
  506     {
  507         $container = new static($values);
  508 
  509         $container['debugger'] = new Debugger();
  510         $container['grav'] = function (Container $container) {
  511             user_error('Calling $grav[\'grav\'] or {{ grav.grav }} is deprecated since Grav 1.6, just use $grav or {{ grav }}', E_USER_DEPRECATED);
  512 
  513             return $container;
  514         };
  515 
  516         $container->measureTime('_services', 'Services', function () use ($container) {
  517             $container->registerServices();
  518         });
  519 
  520         return $container;
  521     }
  522 
  523     /**
  524      * Register all services
  525      * Services are defined in the diMap. They can either only the class
  526      * of a Service Provider or a pair of serviceKey => serviceClass that
  527      * gets directly mapped into the container.
  528      *
  529      * @return void
  530      */
  531     protected function registerServices()
  532     {
  533         foreach (self::$diMap as $serviceKey => $serviceClass) {
  534             if (\is_int($serviceKey)) {
  535                 $this->register(new $serviceClass);
  536             } else {
  537                 $this[$serviceKey] = function ($c) use ($serviceClass) {
  538                     return new $serviceClass($c);
  539                 };
  540             }
  541         }
  542     }
  543 
  544     /**
  545      * This attempts to find media, other files, and download them
  546      *
  547      * @param string $path
  548      */
  549     public function fallbackUrl($path)
  550     {
  551         $this->fireEvent('onPageFallBackUrl');
  552 
  553         /** @var Uri $uri */
  554         $uri = $this['uri'];
  555 
  556         /** @var Config $config */
  557         $config = $this['config'];
  558 
  559         $uri_extension = strtolower($uri->extension());
  560         $fallback_types = $config->get('system.media.allowed_fallback_types', null);
  561         $supported_types = $config->get('media.types');
  562 
  563         // Check whitelist first, then ensure extension is a valid media type
  564         if (!empty($fallback_types) && !\in_array($uri_extension, $fallback_types, true)) {
  565             return false;
  566         }
  567         if (!array_key_exists($uri_extension, $supported_types)) {
  568             return false;
  569         }
  570 
  571         $path_parts = pathinfo($path);
  572 
  573         /** @var PageInterface $page */
  574         $page = $this['pages']->dispatch($path_parts['dirname'], true);
  575 
  576         if ($page) {
  577             $media = $page->media()->all();
  578             $parsed_url = parse_url(rawurldecode($uri->basename()));
  579             $media_file = $parsed_url['path'];
  580 
  581             // if this is a media object, try actions first
  582             if (isset($media[$media_file])) {
  583                 /** @var Medium $medium */
  584                 $medium = $media[$media_file];
  585                 foreach ($uri->query(null, true) as $action => $params) {
  586                     if (\in_array($action, ImageMedium::$magic_actions, true)) {
  587                         \call_user_func_array([&$medium, $action], explode(',', $params));
  588                     }
  589                 }
  590                 Utils::download($medium->path(), false);
  591             }
  592 
  593             // unsupported media type, try to download it...
  594             if ($uri_extension) {
  595                 $extension = $uri_extension;
  596             } else {
  597                 if (isset($path_parts['extension'])) {
  598                     $extension = $path_parts['extension'];
  599                 } else {
  600                     $extension = null;
  601                 }
  602             }
  603 
  604             if ($extension) {
  605                 $download = true;
  606                 if (\in_array(ltrim($extension, '.'), $config->get('system.media.unsupported_inline_types', []), true)) {
  607                     $download = false;
  608                 }
  609                 Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
  610             }
  611 
  612             // Nothing found
  613             return false;
  614         }
  615 
  616         return $page;
  617     }
  618 }