InitializeProcessor.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. <?php
  2. /**
  3. * @package Grav\Common\Processors
  4. *
  5. * @copyright Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\Processors;
  9. use Grav\Common\Config\Config;
  10. use Grav\Common\Debugger;
  11. use Grav\Common\Errors\Errors;
  12. use Grav\Common\Grav;
  13. use Grav\Common\Page\Pages;
  14. use Grav\Common\Plugins;
  15. use Grav\Common\Session;
  16. use Grav\Common\Uri;
  17. use Grav\Common\Utils;
  18. use Grav\Framework\File\Formatter\YamlFormatter;
  19. use Grav\Framework\File\YamlFile;
  20. use Grav\Framework\Psr7\Response;
  21. use Grav\Framework\Session\Exceptions\SessionException;
  22. use Monolog\Formatter\LineFormatter;
  23. use Monolog\Handler\SyslogHandler;
  24. use Monolog\Logger;
  25. use Psr\Http\Message\RequestInterface;
  26. use Psr\Http\Message\ResponseInterface;
  27. use Psr\Http\Message\ServerRequestInterface;
  28. use Psr\Http\Server\RequestHandlerInterface;
  29. use function defined;
  30. use function in_array;
  31. /**
  32. * Class InitializeProcessor
  33. * @package Grav\Common\Processors
  34. */
  35. class InitializeProcessor extends ProcessorBase
  36. {
  37. /** @var string */
  38. public $id = '_init';
  39. /** @var string */
  40. public $title = 'Initialize';
  41. /** @var bool */
  42. private static $cli_initialized = false;
  43. /**
  44. * @param Grav $grav
  45. * @return void
  46. */
  47. public static function initializeCli(Grav $grav)
  48. {
  49. if (!static::$cli_initialized) {
  50. static::$cli_initialized = true;
  51. $instance = new static($grav);
  52. $instance->processCli();
  53. }
  54. }
  55. /**
  56. * @param ServerRequestInterface $request
  57. * @param RequestHandlerInterface $handler
  58. * @return ResponseInterface
  59. */
  60. public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
  61. {
  62. $this->startTimer('_init', 'Initialize');
  63. // Load configuration.
  64. $config = $this->initializeConfig();
  65. // Initialize logger.
  66. $this->initializeLogger($config);
  67. // Initialize error handlers.
  68. $this->initializeErrors();
  69. // Initialize debugger.
  70. $debugger = $this->initializeDebugger();
  71. // Debugger can return response right away.
  72. $response = $this->handleDebuggerRequest($debugger, $request);
  73. if ($response) {
  74. $this->stopTimer('_init');
  75. return $response;
  76. }
  77. // Initialize output buffering.
  78. $this->initializeOutputBuffering($config);
  79. // Set timezone, locale.
  80. $this->initializeLocale($config);
  81. // Load plugins.
  82. $this->initializePlugins();
  83. // Load pages.
  84. $this->initializePages($config);
  85. // Initialize URI.
  86. $this->initializeUri($config);
  87. // Grav may return redirect response right away.
  88. $redirectCode = (int)$config->get('system.pages.redirect_trailing_slash', 1);
  89. if ($redirectCode) {
  90. $response = $this->handleRedirectRequest($request, $redirectCode > 300 ? $redirectCode : null);
  91. if ($response) {
  92. $this->stopTimer('_init');
  93. return $response;
  94. }
  95. }
  96. // Load accounts (decides class to be used).
  97. // TODO: remove in 2.0.
  98. $this->container['accounts'];
  99. // Initialize session.
  100. $this->initializeSession($config);
  101. $this->stopTimer('_init');
  102. // Wrap call to next handler so that debugger can profile it.
  103. /** @var Response $response */
  104. $response = $debugger->profile(static function () use ($handler, $request) {
  105. return $handler->handle($request);
  106. });
  107. // Log both request and response and return the response.
  108. return $debugger->logRequest($request, $response);
  109. }
  110. public function processCli(): void
  111. {
  112. // Load configuration.
  113. $config = $this->initializeConfig();
  114. // Initialize logger.
  115. $this->initializeLogger($config);
  116. // Disable debugger.
  117. $this->container['debugger']->enabled(false);
  118. // Set timezone, locale.
  119. $this->initializeLocale($config);
  120. // Load plugins.
  121. $this->initializePlugins();
  122. // Load pages.
  123. $this->initializePages($config);
  124. // Initialize URI.
  125. $this->initializeUri($config);
  126. // Load accounts (decides class to be used).
  127. // TODO: remove in 2.0.
  128. $this->container['accounts'];
  129. }
  130. /**
  131. * @return Config
  132. */
  133. protected function initializeConfig(): Config
  134. {
  135. $this->startTimer('_init_config', 'Configuration');
  136. // Initialize Configuration
  137. $grav = $this->container;
  138. /** @var Config $config */
  139. $config = $grav['config'];
  140. $config->init();
  141. $grav['plugins']->setup();
  142. if (defined('GRAV_SCHEMA') && $config->get('versions') === null) {
  143. $filename = GRAV_ROOT . '/user/config/versions.yaml';
  144. if (!is_file($filename)) {
  145. $versions = [
  146. 'core' => [
  147. 'grav' => [
  148. 'version' => GRAV_VERSION,
  149. 'schema' => GRAV_SCHEMA
  150. ]
  151. ]
  152. ];
  153. $config->set('versions', $versions);
  154. $file = new YamlFile($filename, new YamlFormatter(['inline' => 4]));
  155. $file->save($versions);
  156. }
  157. }
  158. // Override configuration using the environment.
  159. $prefix = 'GRAV_CONFIG';
  160. $env = getenv($prefix);
  161. if ($env) {
  162. $cPrefix = $prefix . '__';
  163. $aPrefix = $prefix . '_ALIAS__';
  164. $cLen = strlen($cPrefix);
  165. $aLen = strlen($aPrefix);
  166. $keys = $aliases = [];
  167. $env = $_ENV + $_SERVER;
  168. foreach ($env as $key => $value) {
  169. if (!str_starts_with($key, $prefix)) {
  170. continue;
  171. }
  172. if (str_starts_with($key, $cPrefix)) {
  173. $key = str_replace('__', '.', substr($key, $cLen));
  174. $keys[$key] = $value;
  175. } elseif (str_starts_with($key, $aPrefix)) {
  176. $key = substr($key, $aLen);
  177. $aliases[$key] = $value;
  178. }
  179. }
  180. $list = [];
  181. foreach ($keys as $key => $value) {
  182. foreach ($aliases as $alias => $real) {
  183. $key = str_replace($alias, $real, $key);
  184. }
  185. $list[$key] = $value;
  186. $config->set($key, $value);
  187. }
  188. }
  189. $this->stopTimer('_init_config');
  190. return $config;
  191. }
  192. /**
  193. * @param Config $config
  194. * @return Logger
  195. */
  196. protected function initializeLogger(Config $config): Logger
  197. {
  198. $this->startTimer('_init_logger', 'Logger');
  199. $grav = $this->container;
  200. // Initialize Logging
  201. /** @var Logger $log */
  202. $log = $grav['log'];
  203. if ($config->get('system.log.handler', 'file') === 'syslog') {
  204. $log->popHandler();
  205. $facility = $config->get('system.log.syslog.facility', 'local6');
  206. $logHandler = new SyslogHandler('grav', $facility);
  207. $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
  208. $logHandler->setFormatter($formatter);
  209. $log->pushHandler($logHandler);
  210. }
  211. $this->stopTimer('_init_logger');
  212. return $log;
  213. }
  214. /**
  215. * @return Errors
  216. */
  217. protected function initializeErrors(): Errors
  218. {
  219. $this->startTimer('_init_errors', 'Error Handlers Reset');
  220. $grav = $this->container;
  221. // Initialize Error Handlers
  222. /** @var Errors $errors */
  223. $errors = $grav['errors'];
  224. $errors->resetHandlers();
  225. $this->stopTimer('_init_errors');
  226. return $errors;
  227. }
  228. /**
  229. * @return Debugger
  230. */
  231. protected function initializeDebugger(): Debugger
  232. {
  233. $this->startTimer('_init_debugger', 'Init Debugger');
  234. $grav = $this->container;
  235. /** @var Debugger $debugger */
  236. $debugger = $grav['debugger'];
  237. $debugger->init();
  238. $this->stopTimer('_init_debugger');
  239. return $debugger;
  240. }
  241. /**
  242. * @param Debugger $debugger
  243. * @param ServerRequestInterface $request
  244. * @return ResponseInterface|null
  245. */
  246. protected function handleDebuggerRequest(Debugger $debugger, ServerRequestInterface $request): ?ResponseInterface
  247. {
  248. // Clockwork integration.
  249. $clockwork = $debugger->getClockwork();
  250. if ($clockwork) {
  251. $server = $request->getServerParams();
  252. // $baseUri = str_replace('\\', '/', dirname(parse_url($server['SCRIPT_NAME'], PHP_URL_PATH)));
  253. // if ($baseUri === '/') {
  254. // $baseUri = '';
  255. // }
  256. $requestTime = $server['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME;
  257. $request = $request->withAttribute('request_time', $requestTime);
  258. // Handle clockwork API calls.
  259. $uri = $request->getUri();
  260. if (Utils::contains($uri->getPath(), '/__clockwork/')) {
  261. return $debugger->debuggerRequest($request);
  262. }
  263. $this->container['clockwork'] = $clockwork;
  264. }
  265. return null;
  266. }
  267. /**
  268. * @param Config $config
  269. */
  270. protected function initializeOutputBuffering(Config $config): void
  271. {
  272. $this->startTimer('_init_ob', 'Initialize Output Buffering');
  273. // Use output buffering to prevent headers from being sent too early.
  274. ob_start();
  275. if ($config->get('system.cache.gzip') && !@ob_start('ob_gzhandler')) {
  276. // Enable zip/deflate with a fallback in case of if browser does not support compressing.
  277. ob_start();
  278. }
  279. $this->stopTimer('_init_ob');
  280. }
  281. /**
  282. * @param Config $config
  283. */
  284. protected function initializeLocale(Config $config): void
  285. {
  286. $this->startTimer('_init_locale', 'Initialize Locale');
  287. // Initialize the timezone.
  288. $timezone = $config->get('system.timezone');
  289. if ($timezone) {
  290. date_default_timezone_set($timezone);
  291. }
  292. $grav = $this->container;
  293. $grav->setLocale();
  294. $this->stopTimer('_init_locale');
  295. }
  296. protected function initializePlugins(): Plugins
  297. {
  298. $this->startTimer('_init_plugins_load', 'Load Plugins');
  299. $grav = $this->container;
  300. /** @var Plugins $plugins */
  301. $plugins = $grav['plugins'];
  302. $plugins->init();
  303. $this->stopTimer('_init_plugins_load');
  304. return $plugins;
  305. }
  306. protected function initializePages(Config $config): Pages
  307. {
  308. $this->startTimer('_init_pages_register', 'Load Pages');
  309. $grav = $this->container;
  310. /** @var Pages $pages */
  311. $pages = $grav['pages'];
  312. // Upgrading from older Grav versions won't work without checking if the method exists.
  313. if (method_exists($pages, 'register')) {
  314. $pages->register();
  315. }
  316. $this->stopTimer('_init_pages_register');
  317. return $pages;
  318. }
  319. protected function initializeUri(Config $config): void
  320. {
  321. $this->startTimer('_init_uri', 'Initialize URI');
  322. $grav = $this->container;
  323. /** @var Uri $uri */
  324. $uri = $grav['uri'];
  325. $uri->init();
  326. $this->stopTimer('_init_uri');
  327. }
  328. protected function handleRedirectRequest(RequestInterface $request, int $code = null): ?ResponseInterface
  329. {
  330. if (!in_array($request->getMethod(), ['GET', 'HEAD'])) {
  331. return null;
  332. }
  333. // Redirect pages with trailing slash if configured to do so.
  334. $uri = $request->getUri();
  335. $path = $uri->getPath() ?: '/';
  336. $root = $this->container['uri']->rootUrl();
  337. if ($path !== $root && $path !== $root . '/' && Utils::endsWith($path, '/')) {
  338. // Use permanent redirect for SEO reasons.
  339. return $this->container->getRedirectResponse((string)$uri->withPath(rtrim($path, '/')), $code);
  340. }
  341. return null;
  342. }
  343. /**
  344. * @param Config $config
  345. */
  346. protected function initializeSession(Config $config): void
  347. {
  348. // FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
  349. if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
  350. $this->startTimer('_init_session', 'Start Session');
  351. /** @var Session $session */
  352. $session = $this->container['session'];
  353. try {
  354. $session->init();
  355. } catch (SessionException $e) {
  356. $session->init();
  357. $message = 'Session corruption detected, restarting session...';
  358. $this->addMessage($message);
  359. $this->container['messages']->add($message, 'error');
  360. }
  361. $this->stopTimer('_init_session');
  362. }
  363. }
  364. }