2019-03-27 08:06:12 -07:00
|
|
|
<?php
|
2020-08-03 11:32:10 +08:00
|
|
|
|
2017-01-20 00:57:53 -08:00
|
|
|
/**
|
2021-07-19 21:32:33 +08:00
|
|
|
* This file is part of CodeIgniter 4 framework.
|
2017-01-20 00:57:53 -08:00
|
|
|
*
|
2020-10-24 16:38:41 +08:00
|
|
|
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
2017-01-20 00:57:53 -08:00
|
|
|
*
|
2021-07-19 21:32:33 +08:00
|
|
|
* For the full copyright and license information, please view
|
|
|
|
* the LICENSE file that was distributed with this source code.
|
2017-01-20 00:57:53 -08:00
|
|
|
*/
|
2018-11-10 02:57:39 -02:00
|
|
|
|
2019-03-27 08:06:12 -07:00
|
|
|
namespace CodeIgniter;
|
|
|
|
|
2019-04-19 17:56:57 +05:30
|
|
|
use Closure;
|
2020-04-23 01:27:54 +07:00
|
|
|
use CodeIgniter\Debug\Timer;
|
|
|
|
use CodeIgniter\Events\Events;
|
2020-08-03 11:32:10 +08:00
|
|
|
use CodeIgniter\Exceptions\FrameworkException;
|
2020-04-23 01:27:54 +07:00
|
|
|
use CodeIgniter\Exceptions\PageNotFoundException;
|
|
|
|
use CodeIgniter\HTTP\CLIRequest;
|
2018-09-25 00:16:49 +09:00
|
|
|
use CodeIgniter\HTTP\DownloadResponse;
|
2021-01-05 22:20:17 +07:00
|
|
|
use CodeIgniter\HTTP\IncomingRequest;
|
2021-09-14 23:05:23 +00:00
|
|
|
use CodeIgniter\HTTP\RedirectResponse;
|
2018-05-08 00:02:34 -05:00
|
|
|
use CodeIgniter\HTTP\Request;
|
2018-07-05 23:11:26 -05:00
|
|
|
use CodeIgniter\HTTP\ResponseInterface;
|
2017-01-15 23:35:14 -06:00
|
|
|
use CodeIgniter\HTTP\URI;
|
2019-10-16 11:48:28 -04:00
|
|
|
use CodeIgniter\Router\Exceptions\RedirectException;
|
2016-04-11 23:40:33 -05:00
|
|
|
use CodeIgniter\Router\RouteCollectionInterface;
|
2021-01-05 22:20:17 +07:00
|
|
|
use CodeIgniter\Router\Router;
|
2020-10-04 00:27:56 +07:00
|
|
|
use Config\App;
|
2020-04-23 01:27:54 +07:00
|
|
|
use Config\Cache;
|
2022-05-30 22:48:39 +07:00
|
|
|
use Config\Kint as KintConfig;
|
2020-04-23 01:27:54 +07:00
|
|
|
use Config\Services;
|
2019-04-19 17:56:57 +05:30
|
|
|
use Exception;
|
2020-10-04 00:27:56 +07:00
|
|
|
use Kint;
|
|
|
|
use Kint\Renderer\CliRenderer;
|
2022-02-05 10:01:59 +09:00
|
|
|
use Kint\Renderer\RichRenderer;
|
2022-07-13 17:16:56 +09:00
|
|
|
use Locale;
|
2022-05-30 10:42:10 +09:00
|
|
|
use LogicException;
|
2016-02-07 23:26:11 -06:00
|
|
|
|
2017-01-15 23:35:14 -06:00
|
|
|
/**
|
|
|
|
* This class is the core of the framework, and will analyse the
|
|
|
|
* request, route it to a controller, and send back the response.
|
|
|
|
* Of course, there are variations to that flow, but this is the brains.
|
|
|
|
*/
|
2016-03-27 11:28:13 +09:00
|
|
|
class CodeIgniter
|
2016-03-18 23:19:33 -05:00
|
|
|
{
|
2021-06-04 22:51:52 +08:00
|
|
|
/**
|
|
|
|
* The current version of CodeIgniter Framework
|
|
|
|
*/
|
2023-02-18 08:48:35 +09:00
|
|
|
public const CI_VERSION = '4.3.2';
|
2021-06-04 22:51:52 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* App startup time.
|
|
|
|
*
|
2022-07-20 17:18:01 +09:00
|
|
|
* @var float|null
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected $startTime;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Total app execution time
|
|
|
|
*
|
|
|
|
* @var float
|
|
|
|
*/
|
|
|
|
protected $totalTime;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Main application configuration
|
|
|
|
*
|
|
|
|
* @var App
|
|
|
|
*/
|
|
|
|
protected $config;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Timer instance.
|
|
|
|
*
|
|
|
|
* @var Timer
|
|
|
|
*/
|
|
|
|
protected $benchmark;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Current request.
|
|
|
|
*
|
2022-04-23 09:30:46 +07:00
|
|
|
* @var CLIRequest|IncomingRequest|Request|null
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected $request;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Current response.
|
|
|
|
*
|
|
|
|
* @var ResponseInterface
|
|
|
|
*/
|
|
|
|
protected $response;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Router to use.
|
|
|
|
*
|
|
|
|
* @var Router
|
|
|
|
*/
|
|
|
|
protected $router;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Controller to use.
|
|
|
|
*
|
2021-06-11 23:46:56 +08:00
|
|
|
* @var Closure|string
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected $controller;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Controller method to invoke.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $method;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Output handler to use.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $output;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cache expiration time
|
|
|
|
*
|
2023-02-23 15:43:08 +09:00
|
|
|
* @var int seconds
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected static $cacheTTL = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request path to use.
|
|
|
|
*
|
|
|
|
* @var string
|
2022-10-10 09:23:55 +09:00
|
|
|
*
|
|
|
|
* @deprecated No longer used.
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected $path;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Should the Response instance "pretend"
|
|
|
|
* to keep from setting headers/cookies/etc
|
|
|
|
*
|
2021-06-08 11:46:56 +08:00
|
|
|
* @var bool
|
2022-09-20 17:12:18 +09:00
|
|
|
*
|
|
|
|
* @deprecated No longer used.
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected $useSafeOutput = false;
|
|
|
|
|
2022-02-03 17:02:19 +09:00
|
|
|
/**
|
2022-02-06 11:09:43 +09:00
|
|
|
* Context
|
|
|
|
* web: Invoked by HTTP request
|
|
|
|
* php-cli: Invoked by CLI via `php public/index.php`
|
|
|
|
*
|
2022-06-11 04:52:38 +08:00
|
|
|
* @phpstan-var 'php-cli'|'web'
|
2022-02-03 17:02:19 +09:00
|
|
|
*/
|
2022-02-19 06:49:57 +07:00
|
|
|
protected ?string $context = null;
|
2022-02-03 17:02:19 +09:00
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
/**
|
|
|
|
* Whether to enable Control Filters.
|
|
|
|
*/
|
|
|
|
protected bool $enableFilters = true;
|
|
|
|
|
2022-10-22 19:20:02 +09:00
|
|
|
/**
|
|
|
|
* Whether to return Response object or send response.
|
|
|
|
*/
|
|
|
|
protected bool $returnResponse = false;
|
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*/
|
|
|
|
public function __construct(App $config)
|
|
|
|
{
|
|
|
|
$this->startTime = microtime(true);
|
|
|
|
$this->config = $config;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles some basic app and environment setup.
|
|
|
|
*/
|
|
|
|
public function initialize()
|
|
|
|
{
|
|
|
|
// Define environment variables
|
|
|
|
$this->detectEnvironment();
|
|
|
|
$this->bootstrapEnvironment();
|
|
|
|
|
|
|
|
// Setup Exception Handling
|
|
|
|
Services::exceptions()->initialize();
|
|
|
|
|
|
|
|
// Run this check for manual installations
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! is_file(COMPOSER_PATH)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->resolvePlatformExtensions(); // @codeCoverageIgnore
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set default locale on the server
|
2022-07-13 17:16:56 +09:00
|
|
|
Locale::setDefault($this->config->defaultLocale ?? 'en');
|
2021-06-04 22:51:52 +08:00
|
|
|
|
|
|
|
// Set default timezone on the server
|
|
|
|
date_default_timezone_set($this->config->appTimezone ?? 'UTC');
|
|
|
|
|
2022-08-29 16:57:20 +09:00
|
|
|
$this->initializeKint();
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks system for missing required PHP extensions.
|
|
|
|
*
|
|
|
|
* @throws FrameworkException
|
|
|
|
*
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
*/
|
|
|
|
protected function resolvePlatformExtensions()
|
|
|
|
{
|
|
|
|
$requiredExtensions = [
|
|
|
|
'intl',
|
|
|
|
'json',
|
|
|
|
'mbstring',
|
|
|
|
];
|
|
|
|
|
|
|
|
$missingExtensions = [];
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
foreach ($requiredExtensions as $extension) {
|
|
|
|
if (! extension_loaded($extension)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$missingExtensions[] = $extension;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($missingExtensions !== []) {
|
2021-06-04 22:51:52 +08:00
|
|
|
throw FrameworkException::forMissingExtension(implode(', ', $missingExtensions));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes Kint
|
|
|
|
*/
|
|
|
|
protected function initializeKint()
|
2022-08-29 16:57:20 +09:00
|
|
|
{
|
|
|
|
if (CI_DEBUG) {
|
|
|
|
$this->autoloadKint();
|
|
|
|
$this->configureKint();
|
2022-08-29 18:56:44 +09:00
|
|
|
} elseif (class_exists(Kint::class)) {
|
|
|
|
// In case that Kint is already loaded via Composer.
|
|
|
|
Kint::$enabled_mode = false;
|
|
|
|
// @codeCoverageIgnore
|
2022-08-29 16:57:20 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
helper('kint');
|
|
|
|
}
|
|
|
|
|
|
|
|
private function autoloadKint(): void
|
2021-06-04 22:51:52 +08:00
|
|
|
{
|
|
|
|
// If we have KINT_DIR it means it's already loaded via composer
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! defined('KINT_DIR')) {
|
2021-06-04 22:51:52 +08:00
|
|
|
spl_autoload_register(function ($class) {
|
|
|
|
$class = explode('\\', $class);
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if (array_shift($class) !== 'Kint') {
|
2021-06-04 22:51:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$file = SYSTEMPATH . 'ThirdParty/Kint/' . implode('/', $class) . '.php';
|
|
|
|
|
2022-05-24 16:53:04 +00:00
|
|
|
if (is_file($file)) {
|
2021-07-11 07:31:21 +07:00
|
|
|
require_once $file;
|
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
require_once SYSTEMPATH . 'ThirdParty/Kint/init.php';
|
|
|
|
}
|
2022-08-29 16:57:20 +09:00
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
|
2022-08-29 16:57:20 +09:00
|
|
|
private function configureKint(): void
|
|
|
|
{
|
2022-06-16 08:55:34 +09:00
|
|
|
/** @var \Config\Kint $config */
|
2022-05-30 22:48:39 +07:00
|
|
|
$config = config(KintConfig::class);
|
2021-06-04 22:51:52 +08:00
|
|
|
|
2021-12-19 16:28:11 +09:00
|
|
|
Kint::$depth_limit = $config->maxDepth;
|
2021-06-04 22:51:52 +08:00
|
|
|
Kint::$display_called_from = $config->displayCalledFrom;
|
|
|
|
Kint::$expanded = $config->expanded;
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! empty($config->plugins) && is_array($config->plugins)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
Kint::$plugins = $config->plugins;
|
|
|
|
}
|
|
|
|
|
2022-02-05 10:01:59 +09:00
|
|
|
$csp = Services::csp();
|
|
|
|
if ($csp->enabled()) {
|
|
|
|
RichRenderer::$js_nonce = $csp->getScriptNonce();
|
|
|
|
RichRenderer::$css_nonce = $csp->getStyleNonce();
|
|
|
|
}
|
2021-12-25 11:57:59 +09:00
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
RichRenderer::$theme = $config->richTheme;
|
|
|
|
RichRenderer::$folder = $config->richFolder;
|
|
|
|
RichRenderer::$sort = $config->richSort;
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! empty($config->richObjectPlugins) && is_array($config->richObjectPlugins)) {
|
2021-12-19 16:28:11 +09:00
|
|
|
RichRenderer::$value_plugins = $config->richObjectPlugins;
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! empty($config->richTabPlugins) && is_array($config->richTabPlugins)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
RichRenderer::$tab_plugins = $config->richTabPlugins;
|
|
|
|
}
|
|
|
|
|
|
|
|
CliRenderer::$cli_colors = $config->cliColors;
|
|
|
|
CliRenderer::$force_utf8 = $config->cliForceUTF8;
|
|
|
|
CliRenderer::$detect_width = $config->cliDetectWidth;
|
|
|
|
CliRenderer::$min_terminal_width = $config->cliMinWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Launch the application!
|
|
|
|
*
|
|
|
|
* This is "the loop" if you will. The main entry point into the script
|
|
|
|
* that gets the required class instances, fires off the filters,
|
|
|
|
* tries to route the response, loads the controller and generally
|
|
|
|
* makes all of the pieces work together.
|
|
|
|
*
|
2022-07-20 11:30:16 +09:00
|
|
|
* @return ResponseInterface|void
|
2022-09-08 14:40:53 +08:00
|
|
|
*
|
|
|
|
* @throws RedirectException
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
2021-07-09 23:13:08 +08:00
|
|
|
public function run(?RouteCollectionInterface $routes = null, bool $returnResponse = false)
|
2021-06-04 22:51:52 +08:00
|
|
|
{
|
2022-10-22 19:20:02 +09:00
|
|
|
$this->returnResponse = $returnResponse;
|
|
|
|
|
2022-05-30 10:42:10 +09:00
|
|
|
if ($this->context === null) {
|
2022-06-11 04:52:38 +08:00
|
|
|
throw new LogicException(
|
|
|
|
'Context must be set before run() is called. If you are upgrading from 4.1.x, '
|
|
|
|
. 'you need to merge `public/index.php` and `spark` file from `vendor/codeigniter4/framework`.'
|
|
|
|
);
|
2022-05-30 10:42:10 +09:00
|
|
|
}
|
2022-02-03 17:02:19 +09:00
|
|
|
|
2022-07-20 12:00:20 +09:00
|
|
|
static::$cacheTTL = 0;
|
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->startBenchmark();
|
|
|
|
|
|
|
|
$this->getRequestObject();
|
|
|
|
$this->getResponseObject();
|
|
|
|
|
|
|
|
$this->forceSecureAccess();
|
|
|
|
|
|
|
|
$this->spoofRequestMethod();
|
|
|
|
|
2022-05-05 10:17:42 +09:00
|
|
|
if ($this->request instanceof IncomingRequest && strtolower($this->request->getMethod()) === 'cli') {
|
2022-02-02 16:18:14 +09:00
|
|
|
$this->response->setStatusCode(405)->setBody('Method Not Allowed');
|
|
|
|
|
2022-10-23 10:48:59 +09:00
|
|
|
if ($this->returnResponse) {
|
2022-10-22 19:20:02 +09:00
|
|
|
return $this->response;
|
|
|
|
}
|
2022-07-20 11:32:17 +09:00
|
|
|
|
|
|
|
$this->sendResponse();
|
|
|
|
|
|
|
|
return;
|
2022-02-02 16:18:14 +09:00
|
|
|
}
|
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
Events::trigger('pre_system');
|
|
|
|
|
|
|
|
// Check for a cached page. Execution will stop
|
|
|
|
// if the page has been cached.
|
2023-02-23 15:44:20 +09:00
|
|
|
$cacheConfig = config(Cache::class);
|
2021-06-04 22:51:52 +08:00
|
|
|
$response = $this->displayCache($cacheConfig);
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($response instanceof ResponseInterface) {
|
|
|
|
if ($returnResponse) {
|
2021-06-04 22:51:52 +08:00
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
2022-09-20 17:12:18 +09:00
|
|
|
$this->response->send();
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->callExit(EXIT_SUCCESS);
|
2021-06-08 01:26:32 +08:00
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
try {
|
2021-06-04 22:51:52 +08:00
|
|
|
return $this->handleRequest($routes, $cacheConfig, $returnResponse);
|
2021-06-07 19:06:26 +08:00
|
|
|
} catch (RedirectException $e) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$logger = Services::logger();
|
|
|
|
$logger->info('REDIRECTED ROUTE at ' . $e->getMessage());
|
|
|
|
|
|
|
|
// If the route is a 'redirect' route, it throws
|
|
|
|
// the exception with the $to as the message
|
|
|
|
$this->response->redirect(base_url($e->getMessage()), 'auto', $e->getCode());
|
2022-10-22 19:20:02 +09:00
|
|
|
|
2022-10-23 10:48:59 +09:00
|
|
|
if ($this->returnResponse) {
|
2022-10-22 19:20:02 +09:00
|
|
|
return $this->response;
|
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
|
|
|
|
$this->sendResponse();
|
|
|
|
|
|
|
|
$this->callExit(EXIT_SUCCESS);
|
2021-06-08 01:26:32 +08:00
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
return;
|
2021-06-07 19:06:26 +08:00
|
|
|
} catch (PageNotFoundException $e) {
|
2022-10-22 19:20:02 +09:00
|
|
|
$return = $this->display404errors($e);
|
|
|
|
|
|
|
|
if ($return instanceof ResponseInterface) {
|
|
|
|
return $return;
|
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set our Response instance to "pretend" mode so that things like
|
|
|
|
* cookies and headers are not actually sent, allowing PHP 7.2+ to
|
|
|
|
* not complain when ini_set() function is used.
|
|
|
|
*
|
|
|
|
* @return $this
|
2022-09-20 17:12:18 +09:00
|
|
|
*
|
|
|
|
* @deprecated No longer used.
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
public function useSafeOutput(bool $safe = true)
|
|
|
|
{
|
|
|
|
$this->useSafeOutput = $safe;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2022-02-03 17:02:19 +09:00
|
|
|
/**
|
|
|
|
* Invoked via php-cli command?
|
|
|
|
*/
|
|
|
|
private function isPhpCli(): bool
|
|
|
|
{
|
|
|
|
return $this->context === 'php-cli';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Web access?
|
|
|
|
*/
|
|
|
|
private function isWeb(): bool
|
|
|
|
{
|
|
|
|
return $this->context === 'web';
|
|
|
|
}
|
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
/**
|
|
|
|
* Disables Controller Filters.
|
|
|
|
*/
|
|
|
|
public function disableFilters(): void
|
|
|
|
{
|
|
|
|
$this->enableFilters = false;
|
|
|
|
}
|
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
/**
|
|
|
|
* Handles the main request logic and fires the controller.
|
|
|
|
*
|
2022-09-08 14:40:53 +08:00
|
|
|
* @return ResponseInterface
|
|
|
|
*
|
2022-02-02 16:18:14 +09:00
|
|
|
* @throws PageNotFoundException
|
2021-06-04 22:51:52 +08:00
|
|
|
* @throws RedirectException
|
2022-10-22 19:20:02 +09:00
|
|
|
*
|
2022-10-24 11:19:06 +09:00
|
|
|
* @deprecated $returnResponse is deprecated.
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function handleRequest(?RouteCollectionInterface $routes, Cache $cacheConfig, bool $returnResponse = false)
|
|
|
|
{
|
2022-10-24 11:19:06 +09:00
|
|
|
$this->returnResponse = $returnResponse;
|
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
$routeFilter = $this->tryToRouteIt($routes);
|
|
|
|
|
2021-11-03 10:43:57 +09:00
|
|
|
$uri = $this->determinePath();
|
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
if ($this->enableFilters) {
|
|
|
|
// Start up the filters
|
|
|
|
$filters = Services::filters();
|
|
|
|
|
|
|
|
// If any filters were specified within the routes file,
|
|
|
|
// we need to ensure it's active for the current request
|
|
|
|
if ($routeFilter !== null) {
|
|
|
|
$multipleFiltersEnabled = config('Feature')->multipleFilters ?? false;
|
|
|
|
if ($multipleFiltersEnabled) {
|
|
|
|
$filters->enableFilters($routeFilter, 'before');
|
|
|
|
$filters->enableFilters($routeFilter, 'after');
|
|
|
|
} else {
|
|
|
|
// for backward compatibility
|
|
|
|
$filters->enableFilter($routeFilter, 'before');
|
|
|
|
$filters->enableFilter($routeFilter, 'after');
|
|
|
|
}
|
2021-09-24 17:31:19 +09:00
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
// Run "before" filters
|
|
|
|
$this->benchmark->start('before_filters');
|
|
|
|
$possibleResponse = $filters->run($uri, 'before');
|
|
|
|
$this->benchmark->stop('before_filters');
|
2021-06-04 22:51:52 +08:00
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
// If a ResponseInterface instance is returned then send it back to the client and stop
|
|
|
|
if ($possibleResponse instanceof ResponseInterface) {
|
2022-10-27 08:22:48 +09:00
|
|
|
return $this->returnResponse ? $possibleResponse : $possibleResponse->send();
|
2022-10-10 07:02:32 +09:00
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
if ($possibleResponse instanceof Request) {
|
|
|
|
$this->request = $possibleResponse;
|
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
$returned = $this->startController();
|
|
|
|
|
|
|
|
// Closure controller has run in startController().
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! is_callable($this->controller)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$controller = $this->createController();
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! method_exists($controller, '_remap') && ! is_callable([$controller, $this->method], false)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
throw PageNotFoundException::forMethodNotFound($this->method);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is there a "post_controller_constructor" event?
|
|
|
|
Events::trigger('post_controller_constructor');
|
|
|
|
|
|
|
|
$returned = $this->runController($controller);
|
2021-06-07 19:06:26 +08:00
|
|
|
} else {
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->benchmark->stop('controller_constructor');
|
|
|
|
$this->benchmark->stop('controller');
|
|
|
|
}
|
|
|
|
|
|
|
|
// If $returned is a string, then the controller output something,
|
|
|
|
// probably a view, instead of echoing it directly. Send it along
|
|
|
|
// so it can be used with the output.
|
|
|
|
$this->gatherOutput($cacheConfig, $returned);
|
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
if ($this->enableFilters) {
|
|
|
|
$filters = Services::filters();
|
|
|
|
$filters->setResponse($this->response);
|
2021-07-04 18:22:15 +02:00
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
// After filter debug toolbar requires 'total_execution'.
|
|
|
|
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');
|
2022-07-20 11:54:12 +09:00
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
// Run "after" filters
|
|
|
|
$this->benchmark->start('after_filters');
|
|
|
|
$response = $filters->run($uri, 'after');
|
|
|
|
$this->benchmark->stop('after_filters');
|
2021-06-04 22:51:52 +08:00
|
|
|
|
2022-10-10 07:02:32 +09:00
|
|
|
if ($response instanceof ResponseInterface) {
|
|
|
|
$this->response = $response;
|
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
2022-08-09 10:47:13 +09:00
|
|
|
// Skip unnecessary processing for special Responses.
|
2022-10-10 07:43:54 +09:00
|
|
|
if (
|
|
|
|
! $this->response instanceof DownloadResponse
|
|
|
|
&& ! $this->response instanceof RedirectResponse
|
|
|
|
) {
|
2022-08-09 10:47:13 +09:00
|
|
|
// Cache it without the performance metrics replaced
|
|
|
|
// so that we can have live speed updates along the way.
|
|
|
|
// Must be run after filters to preserve the Response headers.
|
|
|
|
if (static::$cacheTTL > 0) {
|
|
|
|
$this->cachePage($cacheConfig);
|
|
|
|
}
|
2022-07-20 11:54:12 +09:00
|
|
|
|
2022-08-09 10:47:13 +09:00
|
|
|
// Update the performance metrics
|
|
|
|
$body = $this->response->getBody();
|
|
|
|
if ($body !== null) {
|
|
|
|
$output = $this->displayPerformanceMetrics($body);
|
|
|
|
$this->response->setBody($output);
|
|
|
|
}
|
2022-07-20 11:54:12 +09:00
|
|
|
|
2022-08-09 10:47:13 +09:00
|
|
|
// Save our current URI as the previous URI in the session
|
|
|
|
// for safer, more accurate use with `previous_url()` helper function.
|
|
|
|
$this->storePreviousURL(current_url(true));
|
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
|
|
|
|
unset($uri);
|
|
|
|
|
2022-10-22 19:20:02 +09:00
|
|
|
if (! $this->returnResponse) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->sendResponse();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is there a post-system event?
|
|
|
|
Events::trigger('post_system');
|
|
|
|
|
|
|
|
return $this->response;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* You can load different configurations depending on your
|
|
|
|
* current environment. Setting the environment also influences
|
|
|
|
* things like logging and error reporting.
|
|
|
|
*
|
|
|
|
* This can be set to anything, but default usage is:
|
|
|
|
*
|
|
|
|
* development
|
|
|
|
* testing
|
|
|
|
* production
|
2021-07-11 07:38:24 +07:00
|
|
|
*
|
|
|
|
* @codeCoverageIgnore
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function detectEnvironment()
|
|
|
|
{
|
|
|
|
// Make sure ENVIRONMENT isn't already set by other means.
|
2021-07-11 07:39:57 +07:00
|
|
|
if (! defined('ENVIRONMENT')) {
|
2022-07-30 12:26:23 -04:00
|
|
|
define('ENVIRONMENT', env('CI_ENVIRONMENT', 'production'));
|
2021-07-11 07:38:24 +07:00
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load any custom boot files based upon the current environment.
|
|
|
|
*
|
|
|
|
* If no boot file exists, we shouldn't continue because something
|
|
|
|
* is wrong. At the very least, they should have error reporting setup.
|
|
|
|
*/
|
|
|
|
protected function bootstrapEnvironment()
|
|
|
|
{
|
2021-06-07 19:06:26 +08:00
|
|
|
if (is_file(APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php')) {
|
2021-06-04 22:51:52 +08:00
|
|
|
require_once APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
|
2021-06-07 19:06:26 +08:00
|
|
|
} else {
|
2021-06-04 22:51:52 +08:00
|
|
|
// @codeCoverageIgnoreStart
|
|
|
|
header('HTTP/1.1 503 Service Unavailable.', true, 503);
|
|
|
|
echo 'The application environment is not set correctly.';
|
2021-06-08 01:26:32 +08:00
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
exit(EXIT_ERROR); // EXIT_ERROR
|
|
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start the Benchmark
|
|
|
|
*
|
|
|
|
* The timer is used to display total script execution both in the
|
|
|
|
* debug toolbar, and potentially on the displayed page.
|
|
|
|
*/
|
|
|
|
protected function startBenchmark()
|
|
|
|
{
|
2021-09-07 23:30:27 +08:00
|
|
|
if ($this->startTime === null) {
|
2021-07-06 12:34:23 +02:00
|
|
|
$this->startTime = microtime(true);
|
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
|
|
|
|
$this->benchmark = Services::timer();
|
|
|
|
$this->benchmark->start('total_execution', $this->startTime);
|
|
|
|
$this->benchmark->start('bootstrap');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets a Request object to be used for this request.
|
|
|
|
* Used when running certain tests.
|
|
|
|
*
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function setRequest(Request $request)
|
|
|
|
{
|
|
|
|
$this->request = $request;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-06-23 11:17:15 +09:00
|
|
|
* Get our Request object, (either IncomingRequest or CLIRequest).
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function getRequestObject()
|
|
|
|
{
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($this->request instanceof Request) {
|
2021-06-04 22:51:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-11 04:52:38 +08:00
|
|
|
if ($this->isPhpCli()) {
|
2022-06-23 11:17:15 +09:00
|
|
|
Services::createRequest($this->config, true);
|
2021-06-07 19:06:26 +08:00
|
|
|
} else {
|
2022-06-23 11:17:15 +09:00
|
|
|
Services::createRequest($this->config);
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
2022-06-23 11:17:15 +09:00
|
|
|
|
|
|
|
$this->request = Services::request();
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get our Response object, and set some default values, including
|
|
|
|
* the HTTP protocol version and a default successful response.
|
|
|
|
*/
|
|
|
|
protected function getResponseObject()
|
|
|
|
{
|
|
|
|
$this->response = Services::response($this->config);
|
|
|
|
|
2022-02-03 17:02:19 +09:00
|
|
|
if ($this->isWeb()) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->response->setProtocolVersion($this->request->getProtocolVersion());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assume success until proven otherwise.
|
|
|
|
$this->response->setStatusCode(200);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Force Secure Site Access? If the config value 'forceGlobalSecureRequests'
|
|
|
|
* is true, will enforce that all requests to this site are made through
|
|
|
|
* HTTPS. Will redirect the user to the current page with HTTPS, as well
|
|
|
|
* as set the HTTP Strict Transport Security header for those browsers
|
|
|
|
* that support it.
|
|
|
|
*
|
2021-06-08 11:46:56 +08:00
|
|
|
* @param int $duration How long the Strict Transport Security
|
2021-06-08 12:11:23 +08:00
|
|
|
* should be enforced for this URL.
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
2021-12-13 22:07:52 +07:00
|
|
|
protected function forceSecureAccess($duration = 31_536_000)
|
2021-06-04 22:51:52 +08:00
|
|
|
{
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($this->config->forceGlobalSecureRequests !== true) {
|
2021-06-04 22:51:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
force_https($duration, $this->request, $this->response);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if a response has been cached for the given URI.
|
|
|
|
*
|
2022-07-20 11:30:16 +09:00
|
|
|
* @return false|ResponseInterface
|
2022-09-08 14:40:53 +08:00
|
|
|
*
|
|
|
|
* @throws Exception
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
public function displayCache(Cache $config)
|
|
|
|
{
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($cachedResponse = cache()->get($this->generateCacheName($config))) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$cachedResponse = unserialize($cachedResponse);
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! is_array($cachedResponse) || ! isset($cachedResponse['output']) || ! isset($cachedResponse['headers'])) {
|
2021-06-04 22:51:52 +08:00
|
|
|
throw new Exception('Error unserializing page cache');
|
|
|
|
}
|
|
|
|
|
|
|
|
$headers = $cachedResponse['headers'];
|
|
|
|
$output = $cachedResponse['output'];
|
|
|
|
|
|
|
|
// Clear all default headers
|
2021-06-07 19:06:26 +08:00
|
|
|
foreach (array_keys($this->response->headers()) as $key) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->response->removeHeader($key);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set cached headers
|
2021-06-07 19:06:26 +08:00
|
|
|
foreach ($headers as $name => $value) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->response->setHeader($name, $value);
|
|
|
|
}
|
|
|
|
|
2022-07-20 11:54:12 +09:00
|
|
|
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');
|
|
|
|
$output = $this->displayPerformanceMetrics($output);
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->response->setBody($output);
|
|
|
|
|
|
|
|
return $this->response;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tells the app that the final output should be cached.
|
|
|
|
*/
|
|
|
|
public static function cache(int $time)
|
|
|
|
{
|
|
|
|
static::$cacheTTL = $time;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Caches the full response from the current request. Used for
|
|
|
|
* full-page caching for very high performance.
|
|
|
|
*
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function cachePage(Cache $config)
|
|
|
|
{
|
|
|
|
$headers = [];
|
2021-06-08 01:26:32 +08:00
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
foreach ($this->response->headers() as $header) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$headers[$header->getName()] = $header->getValueLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
return cache()->save($this->generateCacheName($config), serialize(['headers' => $headers, 'output' => $this->output]), static::$cacheTTL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array with our basic performance stats collected.
|
|
|
|
*/
|
|
|
|
public function getPerformanceStats(): array
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
'startTime' => $this->startTime,
|
|
|
|
'totalTime' => $this->totalTime,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generates the cache name to use for our full-page caching.
|
|
|
|
*/
|
|
|
|
protected function generateCacheName(Cache $config): string
|
|
|
|
{
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($this->request instanceof CLIRequest) {
|
2021-06-04 22:51:52 +08:00
|
|
|
return md5($this->request->getPath());
|
|
|
|
}
|
|
|
|
|
2022-09-05 14:38:09 +08:00
|
|
|
$uri = clone $this->request->getUri();
|
|
|
|
|
|
|
|
$query = $config->cacheQueryString
|
|
|
|
? $uri->getQuery(is_array($config->cacheQueryString) ? ['only' => $config->cacheQueryString] : [])
|
|
|
|
: '';
|
2021-06-04 22:51:52 +08:00
|
|
|
|
2022-09-05 14:38:09 +08:00
|
|
|
return md5($uri->setFragment('')->setQuery($query));
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-07-17 15:50:50 +02:00
|
|
|
* Replaces the elapsed_time tag.
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
public function displayPerformanceMetrics(string $output): string
|
|
|
|
{
|
|
|
|
return str_replace('{elapsed_time}', (string) $this->totalTime, $output);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Try to Route It - As it sounds like, works with the router to
|
|
|
|
* match a route against the current URI. If the route is a
|
|
|
|
* "redirect route", will also handle the redirect.
|
|
|
|
*
|
|
|
|
* @param RouteCollectionInterface|null $routes An collection interface to use in place
|
2021-06-08 12:11:23 +08:00
|
|
|
* of the config file.
|
2021-06-04 22:51:52 +08:00
|
|
|
*
|
2022-01-28 10:36:08 +09:00
|
|
|
* @return string|string[]|null Route filters, that is, the filters specified in the routes file
|
2022-09-08 14:40:53 +08:00
|
|
|
*
|
|
|
|
* @throws RedirectException
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
2021-07-09 23:13:08 +08:00
|
|
|
protected function tryToRouteIt(?RouteCollectionInterface $routes = null)
|
2021-06-04 22:51:52 +08:00
|
|
|
{
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($routes === null) {
|
2022-07-24 20:22:41 +09:00
|
|
|
$routes = Services::routes()->loadRoutes();
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// $routes is defined in Config/Routes.php
|
|
|
|
$this->router = Services::router($routes, $this->request);
|
|
|
|
|
|
|
|
$path = $this->determinePath();
|
|
|
|
|
|
|
|
$this->benchmark->stop('bootstrap');
|
|
|
|
$this->benchmark->start('routing');
|
|
|
|
|
|
|
|
ob_start();
|
|
|
|
|
|
|
|
$this->controller = $this->router->handle($path);
|
|
|
|
$this->method = $this->router->methodName();
|
|
|
|
|
|
|
|
// If a {locale} segment was matched in the final route,
|
|
|
|
// then we need to set the correct locale on our Request.
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($this->router->hasLocale()) {
|
2021-12-13 15:05:13 +08:00
|
|
|
$this->request->setLocale($this->router->getLocale());
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->benchmark->stop('routing');
|
|
|
|
|
2021-09-24 17:31:19 +09:00
|
|
|
// for backward compatibility
|
|
|
|
$multipleFiltersEnabled = config('Feature')->multipleFilters ?? false;
|
|
|
|
if (! $multipleFiltersEnabled) {
|
|
|
|
return $this->router->getFilter();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->router->getFilters();
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines the path to use for us to try to route to, based
|
2022-10-10 09:23:55 +09:00
|
|
|
* on the CLI/IncomingRequest path.
|
2022-04-08 10:10:11 +09:00
|
|
|
*
|
|
|
|
* @return string
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function determinePath()
|
|
|
|
{
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! empty($this->path)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
return $this->path;
|
|
|
|
}
|
|
|
|
|
|
|
|
return method_exists($this->request, 'getPath') ? $this->request->getPath() : $this->request->getUri()->getPath();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allows the request path to be set from outside the class,
|
|
|
|
* instead of relying on CLIRequest or IncomingRequest for the path.
|
|
|
|
*
|
2022-10-10 09:23:55 +09:00
|
|
|
* This is not used now.
|
2021-06-04 22:51:52 +08:00
|
|
|
*
|
|
|
|
* @return $this
|
2022-10-10 09:23:55 +09:00
|
|
|
*
|
|
|
|
* @deprecated No longer used.
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
public function setPath(string $path)
|
|
|
|
{
|
|
|
|
$this->path = $path;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Now that everything has been setup, this method attempts to run the
|
|
|
|
* controller method and make the script go. If it's not able to, will
|
|
|
|
* show the appropriate Page Not Found error.
|
2022-02-17 09:17:38 +09:00
|
|
|
*
|
|
|
|
* @return ResponseInterface|string|void
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function startController()
|
|
|
|
{
|
|
|
|
$this->benchmark->start('controller');
|
|
|
|
$this->benchmark->start('controller_constructor');
|
|
|
|
|
|
|
|
// Is it routed to a Closure?
|
2021-06-07 19:06:26 +08:00
|
|
|
if (is_object($this->controller) && (get_class($this->controller) === 'Closure')) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$controller = $this->controller;
|
2021-06-08 01:26:32 +08:00
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
return $controller(...$this->router->params());
|
|
|
|
}
|
|
|
|
|
|
|
|
// No controller specified - we don't know what to do now.
|
2021-06-07 19:06:26 +08:00
|
|
|
if (empty($this->controller)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
throw PageNotFoundException::forEmptyController();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to autoload the class
|
2021-06-07 19:06:26 +08:00
|
|
|
if (! class_exists($this->controller, true) || $this->method[0] === '_') {
|
2021-06-04 22:51:52 +08:00
|
|
|
throw PageNotFoundException::forControllerNotFound($this->controller, $this->method);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Instantiates the controller class.
|
|
|
|
*
|
2022-02-17 09:17:38 +09:00
|
|
|
* @return Controller
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function createController()
|
|
|
|
{
|
2022-09-12 16:50:15 +09:00
|
|
|
assert(is_string($this->controller));
|
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
$class = new $this->controller();
|
|
|
|
$class->initController($this->request, $this->response, Services::logger());
|
|
|
|
|
|
|
|
$this->benchmark->stop('controller_constructor');
|
|
|
|
|
|
|
|
return $class;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Runs the controller, allowing for _remap methods to function.
|
|
|
|
*
|
2021-10-16 14:36:37 +09:00
|
|
|
* CI4 supports three types of requests:
|
|
|
|
* 1. Web: URI segments become parameters, sent to Controllers via Routes,
|
|
|
|
* output controlled by Headers to browser
|
2022-06-11 04:52:38 +08:00
|
|
|
* 2. PHP CLI: accessed by CLI via php public/index.php, arguments become URI segments,
|
2021-10-16 14:36:37 +09:00
|
|
|
* sent to Controllers via Routes, output varies
|
|
|
|
*
|
2021-06-04 22:51:52 +08:00
|
|
|
* @param mixed $class
|
|
|
|
*
|
2022-02-17 09:17:38 +09:00
|
|
|
* @return false|ResponseInterface|string|void
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function runController($class)
|
|
|
|
{
|
2022-06-11 04:52:38 +08:00
|
|
|
// This is a Web request or PHP CLI request
|
|
|
|
$params = $this->router->params();
|
2021-06-04 22:51:52 +08:00
|
|
|
|
2022-06-11 04:52:38 +08:00
|
|
|
$output = method_exists($class, '_remap')
|
|
|
|
? $class->_remap($this->method, ...$params)
|
|
|
|
: $class->{$this->method}(...$params);
|
2021-10-16 14:36:37 +09:00
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->benchmark->stop('controller');
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Displays a 404 Page Not Found error. If set, will try to
|
|
|
|
* call the 404Override controller/method that was set in routing config.
|
2022-10-22 19:20:02 +09:00
|
|
|
*
|
|
|
|
* @return ResponseInterface|void
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function display404errors(PageNotFoundException $e)
|
|
|
|
{
|
|
|
|
// Is there a 404 Override available?
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($override = $this->router->get404Override()) {
|
2022-02-17 09:11:04 +09:00
|
|
|
$returned = null;
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($override instanceof Closure) {
|
2021-06-04 22:51:52 +08:00
|
|
|
echo $override($e->getMessage());
|
2021-06-07 19:06:26 +08:00
|
|
|
} elseif (is_array($override)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->benchmark->start('controller');
|
|
|
|
$this->benchmark->start('controller_constructor');
|
|
|
|
|
|
|
|
$this->controller = $override[0];
|
|
|
|
$this->method = $override[1];
|
|
|
|
|
|
|
|
$controller = $this->createController();
|
2022-02-17 09:11:04 +09:00
|
|
|
$returned = $this->runController($controller);
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
unset($override);
|
|
|
|
|
|
|
|
$cacheConfig = new Cache();
|
2022-02-17 09:11:04 +09:00
|
|
|
$this->gatherOutput($cacheConfig, $returned);
|
2022-10-23 10:48:59 +09:00
|
|
|
if ($this->returnResponse) {
|
2022-10-22 19:20:02 +09:00
|
|
|
return $this->response;
|
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
|
|
|
|
$this->sendResponse();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Display 404 Errors
|
|
|
|
$this->response->setStatusCode($e->getCode());
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if (ENVIRONMENT !== 'testing') {
|
|
|
|
if (ob_get_level() > 0) {
|
2022-09-02 09:33:28 +08:00
|
|
|
ob_end_flush(); // @codeCoverageIgnore
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// When testing, one is for phpunit, another is for test case.
|
2021-06-07 19:06:26 +08:00
|
|
|
elseif (ob_get_level() > 2) {
|
2021-06-04 22:51:52 +08:00
|
|
|
ob_end_flush(); // @codeCoverageIgnore
|
|
|
|
}
|
|
|
|
|
2022-08-01 16:46:38 +09:00
|
|
|
// Throws new PageNotFoundException and remove exception message on production.
|
2022-02-03 17:02:19 +09:00
|
|
|
throw PageNotFoundException::forPageNotFound(
|
2022-06-29 17:52:08 +09:00
|
|
|
(ENVIRONMENT !== 'production' || ! $this->isWeb()) ? $e->getMessage() : null
|
2022-02-03 17:02:19 +09:00
|
|
|
);
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gathers the script output from the buffer, replaces some execution
|
|
|
|
* time tag in the output and displays the debug toolbar, if required.
|
|
|
|
*
|
2022-07-20 11:54:12 +09:00
|
|
|
* @param Cache|null $cacheConfig Deprecated. No longer used.
|
2022-02-17 09:17:38 +09:00
|
|
|
* @param ResponseInterface|string|null $returned
|
2022-07-20 11:54:12 +09:00
|
|
|
*
|
|
|
|
* @deprecated $cacheConfig is deprecated.
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
2021-07-09 23:13:08 +08:00
|
|
|
protected function gatherOutput(?Cache $cacheConfig = null, $returned = null)
|
2021-06-04 22:51:52 +08:00
|
|
|
{
|
|
|
|
$this->output = ob_get_contents();
|
|
|
|
// If buffering is not null.
|
|
|
|
// Clean (erase) the output buffer and turn off output buffering
|
2021-06-07 19:06:26 +08:00
|
|
|
if (ob_get_length()) {
|
2021-06-04 22:51:52 +08:00
|
|
|
ob_end_clean();
|
|
|
|
}
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($returned instanceof DownloadResponse) {
|
2022-01-27 09:28:31 +09:00
|
|
|
// Turn off output buffering completely, even if php.ini output_buffering is not off
|
2022-08-13 10:04:35 +09:00
|
|
|
if (ENVIRONMENT !== 'testing') {
|
|
|
|
while (ob_get_level() > 0) {
|
|
|
|
ob_end_clean();
|
|
|
|
}
|
2022-01-27 09:28:31 +09:00
|
|
|
}
|
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->response = $returned;
|
2021-06-08 01:26:32 +08:00
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// If the controller returned a response object,
|
|
|
|
// we need to grab the body from it so it can
|
|
|
|
// be added to anything else that might have been
|
|
|
|
// echoed already.
|
|
|
|
// We also need to save the instance locally
|
|
|
|
// so that any status code changes, etc, take place.
|
2021-06-07 19:06:26 +08:00
|
|
|
if ($returned instanceof ResponseInterface) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->response = $returned;
|
|
|
|
$returned = $returned->getBody();
|
|
|
|
}
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if (is_string($returned)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$this->output .= $returned;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->response->setBody($this->output);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If we have a session object to use, store the current URI
|
|
|
|
* as the previous URI. This is called just prior to sending the
|
|
|
|
* response to the client, and will make it available next request.
|
|
|
|
*
|
|
|
|
* This helps provider safer, more reliable previous_url() detection.
|
|
|
|
*
|
2021-06-11 23:46:56 +08:00
|
|
|
* @param string|URI $uri
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
public function storePreviousURL($uri)
|
|
|
|
{
|
|
|
|
// Ignore CLI requests
|
2022-02-03 17:02:19 +09:00
|
|
|
if (! $this->isWeb()) {
|
|
|
|
return;
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
// Ignore AJAX requests
|
2021-06-07 19:06:26 +08:00
|
|
|
if (method_exists($this->request, 'isAJAX') && $this->request->isAJAX()) {
|
2021-06-04 22:51:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-09-14 23:05:23 +00:00
|
|
|
// Ignore unroutable responses
|
|
|
|
if ($this->response instanceof DownloadResponse || $this->response instanceof RedirectResponse) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-21 18:46:13 +09:00
|
|
|
// Ignore non-HTML responses
|
|
|
|
if (strpos($this->response->getHeaderLine('Content-Type'), 'text/html') === false) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-04 22:51:52 +08:00
|
|
|
// This is mainly needed during testing...
|
2021-06-07 19:06:26 +08:00
|
|
|
if (is_string($uri)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
$uri = new URI($uri);
|
|
|
|
}
|
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if (isset($_SESSION)) {
|
2022-10-29 08:31:25 +09:00
|
|
|
$_SESSION['_ci_previous_url'] = URI::createURIString(
|
|
|
|
$uri->getScheme(),
|
|
|
|
$uri->getAuthority(),
|
|
|
|
$uri->getPath(),
|
|
|
|
$uri->getQuery(),
|
|
|
|
$uri->getFragment()
|
|
|
|
);
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Modifies the Request Object to use a different method if a POST
|
|
|
|
* variable called _method is found.
|
|
|
|
*/
|
|
|
|
public function spoofRequestMethod()
|
|
|
|
{
|
|
|
|
// Only works with POSTED forms
|
2022-05-05 10:17:42 +09:00
|
|
|
if (strtolower($this->request->getMethod()) !== 'post') {
|
2021-06-04 22:51:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-12-13 15:05:13 +08:00
|
|
|
$method = $this->request->getPost('_method');
|
2021-06-04 22:51:52 +08:00
|
|
|
|
2021-06-07 19:06:26 +08:00
|
|
|
if (empty($method)) {
|
2021-06-04 22:51:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-02-09 11:40:48 +09:00
|
|
|
// Only allows PUT, PATCH, DELETE
|
|
|
|
if (in_array(strtoupper($method), ['PUT', 'PATCH', 'DELETE'], true)) {
|
|
|
|
$this->request = $this->request->setMethod($method);
|
|
|
|
}
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends the output of this request back to the client.
|
|
|
|
* This is what they've been waiting for!
|
2022-07-20 11:30:16 +09:00
|
|
|
*
|
|
|
|
* @return void
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function sendResponse()
|
|
|
|
{
|
2022-09-20 17:12:18 +09:00
|
|
|
$this->response->send();
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Exits the application, setting the exit code for CLI-based applications
|
|
|
|
* that might be watching.
|
|
|
|
*
|
|
|
|
* Made into a separate method so that it can be mocked during testing
|
|
|
|
* without actually stopping script execution.
|
|
|
|
*
|
2021-06-08 11:46:56 +08:00
|
|
|
* @param int $code
|
2021-06-04 22:51:52 +08:00
|
|
|
*/
|
|
|
|
protected function callExit($code)
|
|
|
|
{
|
2021-07-19 21:08:51 +08:00
|
|
|
exit($code); // @codeCoverageIgnore
|
2021-06-04 22:51:52 +08:00
|
|
|
}
|
2022-02-03 17:02:19 +09:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the app context.
|
|
|
|
*
|
2022-06-11 04:52:38 +08:00
|
|
|
* @phpstan-param 'php-cli'|'web' $context
|
2022-02-06 11:09:43 +09:00
|
|
|
*
|
2022-02-03 17:02:19 +09:00
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function setContext(string $context)
|
|
|
|
{
|
|
|
|
$this->context = $context;
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
2016-02-02 00:24:31 -06:00
|
|
|
}
|