InitializeProcessor.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. <?php
  2. /**
  3. * @package Grav\Common\Processors
  4. *
  5. * @copyright Copyright (c) 2015 - 2022 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. protected 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. // Load accounts (decides class to be used).
  86. // TODO: remove in 2.0.
  87. $this->container['accounts'];
  88. // Initialize session (used by URI, see issue #3269).
  89. $this->initializeSession($config);
  90. // Initialize URI (uses session, see issue #3269).
  91. $this->initializeUri($config);
  92. // Grav may return redirect response right away.
  93. $redirectCode = (int)$config->get('system.pages.redirect_trailing_slash', 1);
  94. if ($redirectCode) {
  95. $response = $this->handleRedirectRequest($request, $redirectCode > 300 ? $redirectCode : null);
  96. if ($response) {
  97. $this->stopTimer('_init');
  98. return $response;
  99. }
  100. }
  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 = USER_DIR . '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. $tag = $config->get('system.log.syslog.tag', 'grav');
  207. $logHandler = new SyslogHandler($tag, $facility);
  208. $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
  209. $logHandler->setFormatter($formatter);
  210. $log->pushHandler($logHandler);
  211. }
  212. $this->stopTimer('_init_logger');
  213. return $log;
  214. }
  215. /**
  216. * @return Errors
  217. */
  218. protected function initializeErrors(): Errors
  219. {
  220. $this->startTimer('_init_errors', 'Error Handlers Reset');
  221. $grav = $this->container;
  222. // Initialize Error Handlers
  223. /** @var Errors $errors */
  224. $errors = $grav['errors'];
  225. $errors->resetHandlers();
  226. $this->stopTimer('_init_errors');
  227. return $errors;
  228. }
  229. /**
  230. * @return Debugger
  231. */
  232. protected function initializeDebugger(): Debugger
  233. {
  234. $this->startTimer('_init_debugger', 'Init Debugger');
  235. $grav = $this->container;
  236. /** @var Debugger $debugger */
  237. $debugger = $grav['debugger'];
  238. $debugger->init();
  239. $this->stopTimer('_init_debugger');
  240. return $debugger;
  241. }
  242. /**
  243. * @param Debugger $debugger
  244. * @param ServerRequestInterface $request
  245. * @return ResponseInterface|null
  246. */
  247. protected function handleDebuggerRequest(Debugger $debugger, ServerRequestInterface $request): ?ResponseInterface
  248. {
  249. // Clockwork integration.
  250. $clockwork = $debugger->getClockwork();
  251. if ($clockwork) {
  252. $server = $request->getServerParams();
  253. // $baseUri = str_replace('\\', '/', dirname(parse_url($server['SCRIPT_NAME'], PHP_URL_PATH)));
  254. // if ($baseUri === '/') {
  255. // $baseUri = '';
  256. // }
  257. $requestTime = $server['REQUEST_TIME_FLOAT'] ?? GRAV_REQUEST_TIME;
  258. $request = $request->withAttribute('request_time', $requestTime);
  259. // Handle clockwork API calls.
  260. $uri = $request->getUri();
  261. if (Utils::contains($uri->getPath(), '/__clockwork/')) {
  262. return $debugger->debuggerRequest($request);
  263. }
  264. $this->container['clockwork'] = $clockwork;
  265. }
  266. return null;
  267. }
  268. /**
  269. * @param Config $config
  270. */
  271. protected function initializeOutputBuffering(Config $config): void
  272. {
  273. $this->startTimer('_init_ob', 'Initialize Output Buffering');
  274. // Use output buffering to prevent headers from being sent too early.
  275. ob_start();
  276. if ($config->get('system.cache.gzip') && !@ob_start('ob_gzhandler')) {
  277. // Enable zip/deflate with a fallback in case of if browser does not support compressing.
  278. ob_start();
  279. }
  280. $this->stopTimer('_init_ob');
  281. }
  282. /**
  283. * @param Config $config
  284. */
  285. protected function initializeLocale(Config $config): void
  286. {
  287. $this->startTimer('_init_locale', 'Initialize Locale');
  288. // Initialize the timezone.
  289. $timezone = $config->get('system.timezone');
  290. if ($timezone) {
  291. date_default_timezone_set($timezone);
  292. }
  293. $grav = $this->container;
  294. $grav->setLocale();
  295. $this->stopTimer('_init_locale');
  296. }
  297. protected function initializePlugins(): Plugins
  298. {
  299. $this->startTimer('_init_plugins_load', 'Load Plugins');
  300. $grav = $this->container;
  301. /** @var Plugins $plugins */
  302. $plugins = $grav['plugins'];
  303. $plugins->init();
  304. $this->stopTimer('_init_plugins_load');
  305. return $plugins;
  306. }
  307. protected function initializePages(Config $config): Pages
  308. {
  309. $this->startTimer('_init_pages_register', 'Load Pages');
  310. $grav = $this->container;
  311. /** @var Pages $pages */
  312. $pages = $grav['pages'];
  313. // Upgrading from older Grav versions won't work without checking if the method exists.
  314. if (method_exists($pages, 'register')) {
  315. $pages->register();
  316. }
  317. $this->stopTimer('_init_pages_register');
  318. return $pages;
  319. }
  320. protected function initializeUri(Config $config): void
  321. {
  322. $this->startTimer('_init_uri', 'Initialize URI');
  323. $grav = $this->container;
  324. /** @var Uri $uri */
  325. $uri = $grav['uri'];
  326. $uri->init();
  327. $this->stopTimer('_init_uri');
  328. }
  329. protected function handleRedirectRequest(RequestInterface $request, int $code = null): ?ResponseInterface
  330. {
  331. if (!in_array($request->getMethod(), ['GET', 'HEAD'])) {
  332. return null;
  333. }
  334. // Redirect pages with trailing slash if configured to do so.
  335. $uri = $request->getUri();
  336. $path = $uri->getPath() ?: '/';
  337. $root = $this->container['uri']->rootUrl();
  338. if ($path !== $root && $path !== $root . '/' && Utils::endsWith($path, '/')) {
  339. // Use permanent redirect for SEO reasons.
  340. return $this->container->getRedirectResponse((string)$uri->withPath(rtrim($path, '/')), $code);
  341. }
  342. return null;
  343. }
  344. /**
  345. * @param Config $config
  346. */
  347. protected function initializeSession(Config $config): void
  348. {
  349. // FIXME: Initialize session should happen later after plugins have been loaded. This is a workaround to fix session issues in AWS.
  350. if (isset($this->container['session']) && $config->get('system.session.initialize', true)) {
  351. $this->startTimer('_init_session', 'Start Session');
  352. /** @var Session $session */
  353. $session = $this->container['session'];
  354. try {
  355. $session->init();
  356. } catch (SessionException $e) {
  357. $session->init();
  358. $message = 'Session corruption detected, restarting session...';
  359. $this->addMessage($message);
  360. $this->container['messages']->add($message, 'error');
  361. }
  362. $this->stopTimer('_init_session');
  363. }
  364. }
  365. }