diff --git a/index.php b/index.php
index c1a2273cd0..6fe4765a7d 100644
--- a/index.php
+++ b/index.php
@@ -4,10 +4,7 @@
*/
declare(strict_types=1);
-use FastRoute\Dispatcher;
-use PhpMyAdmin\Message;
-use PhpMyAdmin\Response;
-use function FastRoute\simpleDispatcher;
+use PhpMyAdmin\Routing;
if (! defined('ROOT_PATH')) {
// phpcs:disable PSR1.Files.SideEffects
@@ -15,52 +12,9 @@ if (! defined('ROOT_PATH')) {
// phpcs:enable
}
-global $containerBuilder, $route;
-
-/** @var string $route */
-$route = $_GET['route'] ?? $_POST['route'] ?? '/';
-
-/**
- * See FAQ 1.34.
- *
- * @see https://docs.phpmyadmin.net/en/latest/faq.html#faq1-34
- */
-if (($route === '/' || $route === '') && isset($_GET['db']) && mb_strlen($_GET['db']) !== 0) {
- $route = '/database/structure';
- if (isset($_GET['table']) && mb_strlen($_GET['table']) !== 0) {
- $route = '/sql';
- }
-}
-
-if ($route === '/import-status') {
- // phpcs:disable PSR1.Files.SideEffects
- define('PMA_MINIMUM_COMMON', true);
- // phpcs:enable
-}
+global $route;
require_once ROOT_PATH . 'libraries/common.inc.php';
-$routes = require ROOT_PATH . 'libraries/routes.php';
-$dispatcher = simpleDispatcher($routes);
-$routeInfo = $dispatcher->dispatch(
- $_SERVER['REQUEST_METHOD'],
- rawurldecode($route)
-);
-if ($routeInfo[0] === Dispatcher::NOT_FOUND) {
- /** @var Response $response */
- $response = $containerBuilder->get(Response::class);
- $response->setHttpResponseCode(404);
- Message::error(sprintf(
- __('Error 404! The page %s was not found.'),
- '' . $route . ''
- ))->display();
-} elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
- /** @var Response $response */
- $response = $containerBuilder->get(Response::class);
- $response->setHttpResponseCode(405);
- Message::error(__('Error 405! Request method not allowed.'))->display();
-} elseif ($routeInfo[0] === Dispatcher::FOUND) {
- [$controllerName, $action] = $routeInfo[1];
- $controller = $containerBuilder->get($controllerName);
- $controller->$action($routeInfo[2]);
-}
+$dispatcher = Routing::getDispatcher();
+Routing::callControllerForRoute($route, $dispatcher);
diff --git a/libraries/cache/.gitignore b/libraries/cache/.gitignore
new file mode 100644
index 0000000000..a68d087bfe
--- /dev/null
+++ b/libraries/cache/.gitignore
@@ -0,0 +1,2 @@
+/*
+!/.gitignore
diff --git a/libraries/classes/Command/CacheWarmupCommand.php b/libraries/classes/Command/CacheWarmupCommand.php
index 87c23f9b6c..a0c0ed32e3 100644
--- a/libraries/classes/Command/CacheWarmupCommand.php
+++ b/libraries/classes/Command/CacheWarmupCommand.php
@@ -6,6 +6,7 @@ namespace PhpMyAdmin\Command;
use PhpMyAdmin\Config;
use PhpMyAdmin\DatabaseInterface;
+use PhpMyAdmin\Routing;
use PhpMyAdmin\Tests\Stubs\DbiDummy;
use PhpMyAdmin\Twig\CoreExtension;
use PhpMyAdmin\Twig\I18nExtension;
@@ -41,13 +42,50 @@ final class CacheWarmupCommand extends Command
protected function configure(): void
{
$this->setDescription('Warms up the Twig templates cache');
+ $this->addOption('twig', null, null, 'Warm up twig templates cache.');
+ $this->addOption('routing', null, null, 'Warm up routing cache.');
$this->setHelp('The %command.name% command warms up the cache of the Twig templates.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ if ($input->getOption('twig') === true && $input->getOption('routing') === true) {
+ $output->writeln('Please specify --twig or --routing');
+ return 1;
+ } elseif ($input->getOption('twig') === true) {
+ return $this->warmUpTwigCache($output);
+ } elseif ($input->getOption('routing') === true) {
+ return $this->warmUpRoutingCache($output);
+ } else {
+ $output->writeln('Warming up all caches.', OutputInterface::VERBOSITY_VERBOSE);
+ $twigCode = $this->warmUptwigCache($output);
+ if ($twigCode !== 0) {
+ $output->writeln('Twig cache generation had an error.');
+ return $twigCode;
+ }
+ $routingCode = $this->warmUpTwigCache($output);
+ if ($routingCode !== 0) {
+ $output->writeln('Routing cache generation had an error.');
+ return $twigCode;
+ }
+ $output->writeln('Warm up of all caches done.', OutputInterface::VERBOSITY_VERBOSE);
+ return 0;
+ }
+ }
+
+ private function warmUpRoutingCache(OutputInterface $output): int
+ {
+ $output->writeln('Warming up the routing cache', OutputInterface::VERBOSITY_VERBOSE);
+ Routing::getDispatcher();
+ $output->writeln('Warm up done.', OutputInterface::VERBOSITY_VERBOSE);
+ return 0;
+ }
+
+ private function warmUpTwigCache(OutputInterface $output): int
{
global $cfg, $PMA_Config, $dbi;
+ $output->writeln('Warming up the twig cache', OutputInterface::VERBOSITY_VERBOSE);
$cfg['environment'] = 'production';
$PMA_Config = new Config(CONFIG_FILE);
$PMA_Config->set('environment', $cfg['environment']);
@@ -77,12 +115,15 @@ final class CacheWarmupCommand extends Command
/** @var CacheInterface $twigCache */
$twigCache = $twig->getCache(false);
+ $output->writeln('Searching for files...', OutputInterface::VERBOSITY_VERY_VERBOSE);
+
$replacements = [];
$templates = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($tplDir),
RecursiveIteratorIterator::LEAVES_ONLY
);
+ $output->writeln('Warming templates', OutputInterface::VERBOSITY_VERY_VERBOSE);
foreach ($templates as $file) {
// Skip test files
if (strpos($file->getPathname(), '/test/') !== false) {
@@ -91,6 +132,7 @@ final class CacheWarmupCommand extends Command
// force compilation
if ($file->isFile() && $file->getExtension() === 'twig') {
$name = str_replace($tplDir . '/', '', $file->getPathname());
+ $output->writeln('Loading: ' . $name, OutputInterface::VERBOSITY_DEBUG);
$template = $twig->loadTemplate($name);
// Generate line map
@@ -101,6 +143,7 @@ final class CacheWarmupCommand extends Command
}
}
+ $output->writeln('Writing replacements...', OutputInterface::VERBOSITY_VERY_VERBOSE);
// Store replacements in JSON
$handle = fopen($tmpDir . '/replace.json', 'w');
if ($handle === false) {
@@ -109,6 +152,7 @@ final class CacheWarmupCommand extends Command
fwrite($handle, (string) json_encode($replacements));
fclose($handle);
+ $output->writeln('Warm up done.', OutputInterface::VERBOSITY_VERBOSE);
return 0;
}
diff --git a/libraries/classes/Display/ImportAjax.php b/libraries/classes/Display/ImportAjax.php
index 476c98226d..002f84d749 100644
--- a/libraries/classes/Display/ImportAjax.php
+++ b/libraries/classes/Display/ImportAjax.php
@@ -45,7 +45,7 @@ class ImportAjax
* list of available plugins
*/
$plugins = [
- // PHP 5.4 session-based upload progress is problematic, see bug 3964
+ // in PHP 5.4 session-based upload progress was problematic, see closed bug 3964
//"session",
'progress',
'apc',
diff --git a/libraries/classes/Routing.php b/libraries/classes/Routing.php
new file mode 100644
index 0000000000..fdf158bb6a
--- /dev/null
+++ b/libraries/classes/Routing.php
@@ -0,0 +1,80 @@
+ CACHE_DIR . 'routes.cache',
+ 'cacheDisabled' => ($cfg['environment'] ?? '') === 'development',
+ ]);
+ }
+
+ public static function getCurrentRoute(): string
+ {
+ /** @var string $route */
+ $route = $_GET['route'] ?? $_POST['route'] ?? '/';
+
+ /**
+ * See FAQ 1.34.
+ *
+ * @see https://docs.phpmyadmin.net/en/latest/faq.html#faq1-34
+ */
+ if (($route === '/' || $route === '') && isset($_GET['db']) && mb_strlen($_GET['db']) !== 0) {
+ $route = '/database/structure';
+ if (isset($_GET['table']) && mb_strlen($_GET['table']) !== 0) {
+ $route = '/sql';
+ }
+ }
+ return $route;
+ }
+
+ /**
+ * Call associated controller for a route using the dispatcher
+ * @param string $route The current route
+ * @param Dispatcher $dispatcher The dispatcher
+ */
+ public static function callControllerForRoute(string $route, Dispatcher $dispatcher): void
+ {
+ global $containerBuilder;
+ $routeInfo = $dispatcher->dispatch(
+ $_SERVER['REQUEST_METHOD'],
+ rawurldecode($route)
+ );
+ if ($routeInfo[0] === Dispatcher::NOT_FOUND) {
+ /** @var Response $response */
+ $response = $containerBuilder->get(Response::class);
+ $response->setHttpResponseCode(404);
+ Message::error(sprintf(
+ __('Error 404! The page %s was not found.'),
+ '' . $route . ''
+ ))->display();
+ } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
+ /** @var Response $response */
+ $response = $containerBuilder->get(Response::class);
+ $response->setHttpResponseCode(405);
+ Message::error(__('Error 405! Request method not allowed.'))->display();
+ } elseif ($routeInfo[0] === Dispatcher::FOUND) {
+ [$controllerName, $action] = $routeInfo[1];
+ $controller = $containerBuilder->get($controllerName);
+ $controller->$action($routeInfo[2]);
+ }
+ }
+}
diff --git a/libraries/common.inc.php b/libraries/common.inc.php
index 1117f6d1f2..6af5caa56f 100644
--- a/libraries/common.inc.php
+++ b/libraries/common.inc.php
@@ -40,6 +40,7 @@ use PhpMyAdmin\Message;
use PhpMyAdmin\MoTranslator\Loader;
use PhpMyAdmin\Plugins\AuthenticationPlugin;
use PhpMyAdmin\Response;
+use PhpMyAdmin\Routing;
use PhpMyAdmin\Sanitize;
use PhpMyAdmin\Session;
use PhpMyAdmin\SqlParser\Lexer;
@@ -50,7 +51,7 @@ use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
-global $containerBuilder, $error_handler, $PMA_Config, $server, $dbi, $lang, $cfg, $isConfigLoading, $auth_plugin;
+global $containerBuilder, $error_handler, $PMA_Config, $server, $dbi, $lang, $cfg, $isConfigLoading, $auth_plugin, $route;
/**
* block attempts to directly run this script
@@ -95,6 +96,14 @@ if (! @is_readable(AUTOLOAD_FILE)) {
}
require_once AUTOLOAD_FILE;
+$route = Routing::getCurrentRoute();
+
+if ($route === '/import-status') {
+ // phpcs:disable PSR1.Files.SideEffects
+ define('PMA_MINIMUM_COMMON', true);
+ // phpcs:enable
+}
+
$containerBuilder = new ContainerBuilder();
$loader = new PhpFileLoader($containerBuilder, new FileLocator(__DIR__));
$loader->load('services_loader.php');
diff --git a/libraries/vendor_config.php b/libraries/vendor_config.php
index 674fedc778..9cae9f1ebe 100644
--- a/libraries/vendor_config.php
+++ b/libraries/vendor_config.php
@@ -79,3 +79,8 @@ define('LOCALE_PATH', ROOT_PATH . 'locale/');
* is used)
*/
define('K_PATH_IMAGES', ROOT_PATH);
+
+/**
+ * Define the cache directory for routing cache an other cache files
+ */
+define('CACHE_DIR', ROOT_PATH . 'libraries/cache/');
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 5be43db5ff..a3f2a10055 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -74,6 +74,7 @@
*/node_modules/*
+ */libraries/cache/*
*/test/sami-config.php
*.twig
*/twig-templates/*
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 544bb6390b..490d8874a5 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -12,6 +12,7 @@ parameters:
excludes_analyse:
- examples/openid.php
- node_modules/*
+ - libraries/cache/*
- test/sami-config.php
- tmp/*
- twig-templates/*
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 1b95f0ee71..db35e32a38 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -25,6 +25,7 @@
examples
node_modules
+ libraries/cache
test
tmp
vendor
diff --git a/scripts/console b/scripts/console
index 678b7aac2d..cfdd9b80e4 100755
--- a/scripts/console
+++ b/scripts/console
@@ -13,6 +13,12 @@ define('PHPMYADMIN', true);
require_once ROOT_PATH . 'libraries/vendor_config.php';
require_once AUTOLOAD_FILE;
+if (! class_exists(Application::class)) {
+ echo 'Be sure to have dev-dependencies installed.' . PHP_EOL;
+ echo 'Command aborted.' . PHP_EOL;
+ exit(1);
+}
+
$application = new Application('phpMyAdmin Console Tool');
$application->add(new AdvisoryRulesCommand());
diff --git a/scripts/create-release.sh b/scripts/create-release.sh
index a053322da8..5b2e17e8ac 100755
--- a/scripts/create-release.sh
+++ b/scripts/create-release.sh
@@ -80,7 +80,7 @@ while [ $# -gt 0 ] ; do
exit 1
fi
elif [ -z "$branch" ] ; then
- branch=`echo $1 | tr -d -c '0-9A-Za-z_-'`
+ branch=`echo $1 | tr -d -c '/0-9A-Za-z_-'`
if [ "x$branch" != "x$1" ] ; then
echo "Invalid branch: $1"
exit 1
@@ -218,11 +218,8 @@ echo "* Removing unneeded files"
# Remove developer information
rm -rf .github
-# Remove phpcs coding standard definition
-rm -rf PMAStandard
-
# Testsuite setup
-rm -f .travis.yml .coveralls.yml .scrutinizer.yml .jshintrc .weblate codecov.yml
+rm -f .travis.yml .scrutinizer.yml .jshintrc .weblate codecov.yml
# Remove readme for github
rm -f README.rst
@@ -243,7 +240,7 @@ if [ ! -d libraries/tcpdf ] ; then
cp composer.json composer.json.backup
echo "* Running composer"
composer config platform.php "$PHP_REQ"
- composer update --no-dev
+ composer update --no-dev --optimize-autoloader
# Parse the required versions from composer.json
PACKAGES_VERSIONS=''
@@ -256,7 +253,7 @@ if [ ! -d libraries/tcpdf ] ; then
do
PACKAGES_VERSIONS="$PACKAGES_VERSIONS $PACKAGES:`awk "/require-dev/ {printline = 1; print; next } printline" composer.json | grep "$PACKAGES" | awk -F [\\"] '{print $4}'`"
done
- composer require --update-no-dev $PACKAGES_VERSIONS
+ composer require --optimize-autoloader --update-no-dev $PACKAGES_VERSIONS
mv composer.json.backup composer.json
echo "* Cleanup of composer packages"
@@ -304,6 +301,11 @@ fi
# Remove Bootstrap theme
rm -rf themes/bootstrap
+composer update
+# Warm up the routing cache
+./scripts/console cache:warmup --routing
+composer update --no-dev --optimize-autoloader
+
# Remove git metadata
rm .git
find . -name .gitignore -print0 | xargs -0 -r rm -f
@@ -329,7 +331,7 @@ if [ $do_test -eq 1 ] ; then
rm -f .phpunit.result.cache
# Remove libs installed for testing
rm -rf build
- composer update --no-dev
+ composer update --no-dev --optimize-autoloader
fi
@@ -351,7 +353,7 @@ for kit in $KITS ; do
# Testsuite
rm -rf test/
rm phpunit.xml.* build.xml
- rm -f .editorconfig .eslintignore .eslintrc.json .stylelintrc.json phpstan.neon.dist phpcs.xml.dist
+ rm -f .editorconfig .eslintignore .jshintrc .eslintrc.json .stylelintrc.json phpstan.neon.dist phpstan-baseline.neon phpcs.xml.dist
# Gettext po files
rm -rf po/
# Documentation source code
diff --git a/scripts/update-po b/scripts/update-po
index 411c30066a..1f8083385d 100755
--- a/scripts/update-po
+++ b/scripts/update-po
@@ -13,7 +13,7 @@ set -e
# Generate Twig template cache in clean dir
rm -rf twig-templates/
-php scripts/console cache:warmup
+php scripts/console cache:warmup --twig
# Update pot (template), ensure that advisor is at the end
LOCS=`ls po/*.po | sed 's@.*/\(.*\)\.po@\1@'`
diff --git a/test/classes/RoutingTest.php b/test/classes/RoutingTest.php
new file mode 100644
index 0000000000..d6860141e5
--- /dev/null
+++ b/test/classes/RoutingTest.php
@@ -0,0 +1,101 @@
+assertInstanceOf(Dispatcher::class, $dispatcher);
+ $this->assertSame([
+ Dispatcher::FOUND,
+ [
+ HomeController::class,
+ 'index',
+ ],
+ [],
+ ], $dispatcher->dispatch('GET', '/'));
+ }
+
+ /**
+ * Test for Routing::getCurrentRoute
+ *
+ * @return void
+ */
+ public function testGetCurrentRouteNoParams(): void
+ {
+ $this->assertSame('/', Routing::getCurrentRoute());
+ }
+
+ /**
+ * Test for Routing::getCurrentRoute
+ *
+ * @return void
+ */
+ public function testGetCurrentRouteGet(): void
+ {
+ $_GET['route'] = '/test';
+ $this->assertSame('/test', Routing::getCurrentRoute());
+ }
+
+ /**
+ * Test for Routing::getCurrentRoute
+ *
+ * @return void
+ */
+ public function testGetCurrentRoutePost(): void
+ {
+ $_POST['route'] = '/testpost';
+ $this->assertSame('/testpost', Routing::getCurrentRoute());
+ }
+
+ /**
+ * Test for Routing::getCurrentRoute
+ *
+ * @return void
+ */
+ public function testGetCurrentRouteGetIsOverPost(): void
+ {
+ $_GET['route'] = '/testget';
+ $_POST['route'] = '/testpost';
+ $this->assertSame('/testget', Routing::getCurrentRoute());
+ }
+
+ /**
+ * Test for Routing::getCurrentRoute
+ *
+ * @return void
+ */
+ public function testGetCurrentRouteRedirectDbStructure(): void
+ {
+ $_GET['db'] = 'testDB';
+ $this->assertSame('/database/structure', Routing::getCurrentRoute());
+ }
+
+ /**
+ * Test for Routing::getCurrentRoute
+ *
+ * @return void
+ */
+ public function testGetCurrentRouteRedirectSql(): void
+ {
+ $_GET['db'] = 'testDB';
+ $_GET['table'] = 'tableTest';
+ $this->assertSame('/sql', Routing::getCurrentRoute());
+ }
+}