PHPStorm has been a little annoying with showing all these code issues. I took some time to analyse most of them and fix whichever were easy to fix. This kind of change should improve future maintenance as developers do not need to wonder what a line of code does. Code that has no impact on functionality should be removed. * Remove redundant variables * Invert condition for better clarity * Remove some of the redundant casts * Replace switch with an if * Fix foreach variable * Remove redundant variable concat * Remove redundant concats * Replace for loop with str_repeat * Remove always true param * Turn property $position into local var * Turn Pdf properties into local vars * Remove unused properties in Pdf * Change ternary into condition * Remove redundant assignments They are immediately overwritten with a different value. * Redundant array append * Collapse if statements into assignments * Use boolean constants instead of variables * Remove unneeded parameter * Remove null-coalesce * Unnecessary assignment * Remove redundant isset * Remove readOnly flag for InsertEdit fields * Remove function_exists and $mode var * Remove duplicate condition * Remove redundant elseif * Remove redundant isset * Use hasBodyParam() * Simplify isRoutesCacheFileValid() Psalm complains but is wrong. https://github.com/phpmyadmin/phpmyadmin/pull/18063 --------- Signed-off-by: Kamil Tekiela <tekiela246@gmail.com>
214 lines
6.6 KiB
PHP
214 lines
6.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace PhpMyAdmin;
|
|
|
|
use FastRoute\DataGenerator\GroupCountBased as DataGeneratorGroupCountBased;
|
|
use FastRoute\Dispatcher;
|
|
use FastRoute\Dispatcher\GroupCountBased as DispatcherGroupCountBased;
|
|
use FastRoute\RouteCollector;
|
|
use FastRoute\RouteParser\Std as RouteParserStd;
|
|
use PhpMyAdmin\Controllers\HomeController;
|
|
use PhpMyAdmin\Controllers\Setup\MainController;
|
|
use PhpMyAdmin\Controllers\Setup\ShowConfigController;
|
|
use PhpMyAdmin\Controllers\Setup\ValidateController;
|
|
use PhpMyAdmin\Http\ServerRequest;
|
|
use Psr\Container\ContainerInterface;
|
|
|
|
use function __;
|
|
use function file_exists;
|
|
use function file_put_contents;
|
|
use function htmlspecialchars;
|
|
use function is_array;
|
|
use function is_readable;
|
|
use function is_writable;
|
|
use function rawurldecode;
|
|
use function sprintf;
|
|
use function trigger_error;
|
|
use function var_export;
|
|
|
|
use const CACHE_DIR;
|
|
use const E_USER_WARNING;
|
|
use const ROOT_PATH;
|
|
|
|
/**
|
|
* Class used to warm up the routing cache and manage routing.
|
|
*/
|
|
class Routing
|
|
{
|
|
public const ROUTES_CACHE_FILE = CACHE_DIR . 'routes.cache.php';
|
|
|
|
public static function getDispatcher(): Dispatcher
|
|
{
|
|
$routes = require ROOT_PATH . 'libraries/routes.php';
|
|
|
|
return self::routesCachedDispatcher($routes);
|
|
}
|
|
|
|
public static function skipCache(): bool
|
|
{
|
|
return ($GLOBALS['cfg']['environment'] ?? '') === 'development';
|
|
}
|
|
|
|
public static function canWriteCache(): bool
|
|
{
|
|
$cacheFileExists = file_exists(self::ROUTES_CACHE_FILE);
|
|
$canWriteFile = is_writable(self::ROUTES_CACHE_FILE);
|
|
if ($cacheFileExists && $canWriteFile) {
|
|
return true;
|
|
}
|
|
|
|
// Write without read does not work, chmod 200 for example
|
|
if (! $cacheFileExists && is_writable(CACHE_DIR) && is_readable(CACHE_DIR)) {
|
|
return true;
|
|
}
|
|
|
|
return $canWriteFile;
|
|
}
|
|
|
|
private static function routesCachedDispatcher(callable $routeDefinitionCallback): Dispatcher
|
|
{
|
|
$skipCache = self::skipCache();
|
|
|
|
// If skip cache is enabled, do not try to read the file
|
|
// If no cache skipping then read it and use it
|
|
if (! $skipCache && file_exists(self::ROUTES_CACHE_FILE)) {
|
|
/** @psalm-suppress MissingFile, UnresolvableInclude, MixedAssignment */
|
|
$dispatchData = require self::ROUTES_CACHE_FILE;
|
|
if (self::isRoutesCacheFileValid($dispatchData)) {
|
|
return new DispatcherGroupCountBased($dispatchData);
|
|
}
|
|
}
|
|
|
|
$routeCollector = new RouteCollector(
|
|
new RouteParserStd(),
|
|
new DataGeneratorGroupCountBased()
|
|
);
|
|
$routeDefinitionCallback($routeCollector);
|
|
|
|
$dispatchData = $routeCollector->getData();
|
|
$canWriteCache = self::canWriteCache();
|
|
|
|
// If skip cache is enabled, do not try to write it
|
|
// If no skip cache then try to write if write is possible
|
|
if (! $skipCache && $canWriteCache) {
|
|
$writeWorks = self::writeCache(
|
|
'<?php return ' . var_export($dispatchData, true) . ';'
|
|
);
|
|
if (! $writeWorks) {
|
|
trigger_error(
|
|
sprintf(
|
|
__(
|
|
'The routing cache could not be written, '
|
|
. 'you need to adjust permissions on the folder/file "%s"'
|
|
),
|
|
self::ROUTES_CACHE_FILE
|
|
),
|
|
E_USER_WARNING
|
|
);
|
|
}
|
|
}
|
|
|
|
return new DispatcherGroupCountBased($dispatchData);
|
|
}
|
|
|
|
public static function writeCache(string $cacheContents): bool
|
|
{
|
|
return @file_put_contents(self::ROUTES_CACHE_FILE, $cacheContents) !== false;
|
|
}
|
|
|
|
/**
|
|
* Call associated controller for a route using the dispatcher
|
|
*/
|
|
public static function callControllerForRoute(
|
|
ServerRequest $request,
|
|
Dispatcher $dispatcher,
|
|
ContainerInterface $container
|
|
): void {
|
|
$route = $request->getRoute();
|
|
$routeInfo = $dispatcher->dispatch($request->getMethod(), rawurldecode($route));
|
|
|
|
if ($routeInfo[0] === Dispatcher::NOT_FOUND) {
|
|
/** @var ResponseRenderer $response */
|
|
$response = $container->get(ResponseRenderer::class);
|
|
$response->setHttpResponseCode(404);
|
|
echo Message::error(sprintf(
|
|
__('Error 404! The page %s was not found.'),
|
|
'<code>' . htmlspecialchars($route) . '</code>'
|
|
))->getDisplay();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
|
|
/** @var ResponseRenderer $response */
|
|
$response = $container->get(ResponseRenderer::class);
|
|
$response->setHttpResponseCode(405);
|
|
echo Message::error(__('Error 405! Request method not allowed.'))->getDisplay();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($routeInfo[0] !== Dispatcher::FOUND) {
|
|
return;
|
|
}
|
|
|
|
/** @psalm-var class-string $controllerName */
|
|
$controllerName = $routeInfo[1];
|
|
/** @var array<string, string> $vars */
|
|
$vars = $routeInfo[2];
|
|
|
|
/**
|
|
* @psalm-var callable(ServerRequest=, array<string, string>=):void $controller
|
|
*/
|
|
$controller = $container->get($controllerName);
|
|
$controller($request, $vars);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $dispatchData
|
|
*
|
|
* @psalm-assert-if-true array[] $dispatchData
|
|
*/
|
|
private static function isRoutesCacheFileValid($dispatchData): bool
|
|
{
|
|
return is_array($dispatchData)
|
|
&& isset($dispatchData[1])
|
|
&& is_array($dispatchData[1])
|
|
&& isset($dispatchData[0]['GET']['/'])
|
|
&& $dispatchData[0]['GET']['/'] === HomeController::class;
|
|
}
|
|
|
|
public static function callSetupController(ServerRequest $request): void
|
|
{
|
|
$route = $request->getRoute();
|
|
if ($route === '/setup' || $route === '/') {
|
|
(new MainController())($request);
|
|
|
|
return;
|
|
}
|
|
|
|
if ($route === '/setup/show-config') {
|
|
(new ShowConfigController())($request);
|
|
|
|
return;
|
|
}
|
|
|
|
if ($route === '/setup/validate') {
|
|
(new ValidateController())($request);
|
|
|
|
return;
|
|
}
|
|
|
|
echo (new Template())->render('error/generic', [
|
|
'lang' => $GLOBALS['lang'] ?? 'en',
|
|
'dir' => $GLOBALS['text_dir'] ?? 'ltr',
|
|
'error_message' => Sanitize::sanitizeMessage(sprintf(
|
|
__('Error 404! The page %s was not found.'),
|
|
'[code]' . htmlspecialchars($route) . '[/code]'
|
|
)),
|
|
]);
|
|
}
|
|
}
|