Create a new controller

Signed-off-by: Kamil Tekiela <tekiela246@gmail.com>
This commit is contained in:
Kamil Tekiela 2025-01-14 18:50:21 +00:00
parent 26c2985f39
commit de3f7bc1dc
10 changed files with 234 additions and 160 deletions

View File

@ -31,6 +31,7 @@ use PhpMyAdmin\Controllers\SchemaExportController;
use PhpMyAdmin\Controllers\Server;
use PhpMyAdmin\Controllers\Setup;
use PhpMyAdmin\Controllers\Sql;
use PhpMyAdmin\Controllers\SyncFavoriteTablesController;
use PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Controllers\TableController;
use PhpMyAdmin\Controllers\ThemesController;
@ -308,7 +309,6 @@ return [
'arguments' => [
'$response' => '@response',
'$template' => '@template',
'$relation' => '@relation',
'$dbTableExists' => '@' . DbTableExists::class,
],
],
@ -1361,5 +1361,12 @@ return [
'$dbTableExists' => '@' . DbTableExists::class,
],
],
SyncFavoriteTablesController::class => [
'class' => SyncFavoriteTablesController::class,
'arguments' => [
'$response' => '@response',
'$relation' => '@relation',
],
],
],
];

View File

@ -2343,12 +2343,6 @@ parameters:
count: 1
path: src/Controllers/Database/Structure/EmptyTableController.php
-
message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#'
identifier: foreach.nonIterable
count: 1
path: src/Controllers/Database/Structure/FavoriteTableController.php
-
message: '''
#^Call to deprecated method getInstance\(\) of class PhpMyAdmin\\Config\:
@ -2358,30 +2352,6 @@ parameters:
count: 1
path: src/Controllers/Database/Structure/FavoriteTableController.php
-
message: '#^Cannot access offset ''db'' on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: src/Controllers/Database/Structure/FavoriteTableController.php
-
message: '#^Cannot access offset ''favorites_synced'' on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: src/Controllers/Database/Structure/FavoriteTableController.php
-
message: '#^Cannot access offset ''table'' on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: src/Controllers/Database/Structure/FavoriteTableController.php
-
message: '#^Cannot access offset int\<0, max\> on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: src/Controllers/Database/Structure/FavoriteTableController.php
-
message: '#^Loose comparison via "\=\=" is not allowed\.$#'
identifier: equal.notAllowed
@ -3657,6 +3627,39 @@ parameters:
count: 1
path: src/Controllers/Sql/SqlController.php
-
message: '''
#^Call to deprecated method getInstance\(\) of class PhpMyAdmin\\Config\:
Use dependency injection instead\.$#
'''
identifier: staticMethod.deprecated
count: 1
path: src/Controllers/SyncFavoriteTablesController.php
-
message: '#^Cannot access offset ''db'' on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: src/Controllers/SyncFavoriteTablesController.php
-
message: '#^Cannot access offset ''favorites_synced'' on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: src/Controllers/SyncFavoriteTablesController.php
-
message: '#^Cannot access offset ''table'' on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: src/Controllers/SyncFavoriteTablesController.php
-
message: '#^Cannot access offset int\<0, max\> on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
path: src/Controllers/SyncFavoriteTablesController.php
-
message: '#^Cannot access offset \(int\|string\) on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
@ -18819,6 +18822,12 @@ parameters:
count: 2
path: tests/unit/Controllers/Sql/SetValuesControllerTest.php
-
message: '#^Parameter \#1 \$str of method PhpMyAdmin\\Dbal\\DatabaseInterface\:\:quoteString\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: tests/unit/Controllers/SyncFavoriteTablesControllerTest.php
-
message: '''
#^Call to deprecated method getInstance\(\) of class PhpMyAdmin\\Config\:

View File

@ -1206,21 +1206,15 @@
<DeprecatedMethod>
<code><![CDATA[Config::getInstance()]]></code>
</DeprecatedMethod>
<MixedArrayAccess>
<code><![CDATA[$value['db']]]></code>
<code><![CDATA[$value['table']]]></code>
</MixedArrayAccess>
<MixedArrayAssignment>
<code><![CDATA[$_SESSION['tmpval']['favorites_synced']]]></code>
<code><![CDATA[$_SESSION['tmpval']['favorites_synced'][Current::$server]]]></code>
</MixedArrayAssignment>
<MixedAssignment>
<code><![CDATA[$favoriteTables]]></code>
<code><![CDATA[$value]]></code>
</MixedAssignment>
<MixedOperand>
<code><![CDATA[Current::$database]]></code>
</MixedOperand>
<PossiblyUnusedMethod>
<code><![CDATA[__construct]]></code>
</PossiblyUnusedMethod>
</file>
<file src="src/Controllers/Database/Structure/RealRowCountController.php">
<MixedArgument>
@ -2260,6 +2254,23 @@
<code><![CDATA[$sqlQuery]]></code>
</MixedAssignment>
</file>
<file src="src/Controllers/SyncFavoriteTablesController.php">
<DeprecatedMethod>
<code><![CDATA[Config::getInstance()]]></code>
</DeprecatedMethod>
<MixedArrayAccess>
<code><![CDATA[$value['db']]]></code>
<code><![CDATA[$value['table']]]></code>
</MixedArrayAccess>
<MixedArrayAssignment>
<code><![CDATA[$_SESSION['tmpval']['favorites_synced']]]></code>
<code><![CDATA[$_SESSION['tmpval']['favorites_synced'][Current::$server]]]></code>
</MixedArrayAssignment>
<MixedAssignment>
<code><![CDATA[$favoriteTables]]></code>
<code><![CDATA[$value]]></code>
</MixedAssignment>
</file>
<file src="src/Controllers/Table/AddFieldController.php">
<MixedArgument>
<code><![CDATA[$_POST['field_input_transformation'][$fieldindex]]]></code>

View File

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Database\Structure;
use PhpMyAdmin\Config;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\Controllers\InvocableController;
use PhpMyAdmin\Current;
use PhpMyAdmin\DbTableExists;
@ -33,7 +32,6 @@ final class FavoriteTableController implements InvocableController
public function __construct(
private readonly ResponseRenderer $response,
private readonly Template $template,
private readonly Relation $relation,
private readonly DbTableExists $dbTableExists,
) {
}
@ -60,20 +58,6 @@ final class FavoriteTableController implements InvocableController
// Required to keep each user's preferences separate.
$user = sha1($config->selectedServer['user']);
// Request for Synchronization of favorite tables.
if ($request->getParam('sync_favorite_tables') !== null) {
$relationParameters = $this->relation->getRelationParameters();
if ($relationParameters->favoriteTablesFeature !== null) {
$this->response->addJSON($this->synchronizeFavoriteTables(
$favoriteInstance,
$user,
$favoriteTables,
));
}
return $this->response->response();
}
$databaseName = DatabaseName::tryFrom($request->getParam('db'));
if ($databaseName === null || ! $this->dbTableExists->selectDatabase($databaseName)) {
$this->response->setRequestStatus(false);
@ -142,35 +126,4 @@ final class FavoriteTableController implements InvocableController
return $this->response->response();
}
/**
* Synchronize favorite tables
*
* @param RecentFavoriteTables $favoriteInstance Instance of this class
* @param string $user The user hash
* @param mixed[] $favoriteTables Existing favorites
*
* @return mixed[]
*/
private function synchronizeFavoriteTables(
RecentFavoriteTables $favoriteInstance,
string $user,
array $favoriteTables,
): array {
if ($favoriteInstance->getTables() === [] && isset($favoriteTables[$user])) {
foreach ($favoriteTables[$user] as $value) {
$favoriteInstance->add(new RecentFavoriteTable(
DatabaseName::from($value['db']),
TableName::from($value['table']),
));
}
}
$favoriteTables[$user] = $favoriteInstance->getTables();
// Set flag when localStorage and pmadb(if present) are in sync.
$_SESSION['tmpval']['favorites_synced'][Current::$server] = true;
return ['favoriteTables' => json_encode($favoriteTables), 'list' => $favoriteInstance->getHtmlList()];
}
}

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Controllers;
use PhpMyAdmin\Config;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\Current;
use PhpMyAdmin\Favorites\RecentFavoriteTable;
use PhpMyAdmin\Favorites\RecentFavoriteTables;
use PhpMyAdmin\Favorites\TableType;
use PhpMyAdmin\Http\Response;
use PhpMyAdmin\Http\ServerRequest;
use PhpMyAdmin\Identifiers\DatabaseName;
use PhpMyAdmin\Identifiers\TableName;
use PhpMyAdmin\ResponseRenderer;
use function is_array;
use function json_decode;
use function json_encode;
use function sha1;
final class SyncFavoriteTablesController implements InvocableController
{
public function __construct(
private readonly ResponseRenderer $response,
private readonly Relation $relation,
) {
}
public function __invoke(ServerRequest $request): Response
{
if (! $request->isAjax()) {
return $this->response->response();
}
$favoriteInstance = RecentFavoriteTables::getInstance(TableType::Favorite);
$favoriteTables = json_decode($request->getParsedBodyParamAsString('favoriteTables'), true);
if (! is_array($favoriteTables)) {
$favoriteTables = [];
}
// Required to keep each user's preferences separate.
$user = sha1(Config::getInstance()->selectedServer['user']);
$relationParameters = $this->relation->getRelationParameters();
if ($relationParameters->favoriteTablesFeature !== null) {
if (
$favoriteInstance->getTables() === []
&& isset($favoriteTables[$user])
&& is_array($favoriteTables[$user])
) {
foreach ($favoriteTables[$user] as $value) {
$favoriteInstance->add(new RecentFavoriteTable(
DatabaseName::from($value['db']),
TableName::from($value['table']),
));
}
}
$favoriteTables[$user] = $favoriteInstance->getTables();
// Set flag when localStorage and pmadb(if present) are in sync.
$_SESSION['tmpval']['favorites_synced'][Current::$server] = true;
$this->response->addJSON([
'favoriteTables' => json_encode($favoriteTables),
'list' => $favoriteInstance->getHtmlList(),
]);
}
return $this->response->response();
}
}

View File

@ -294,7 +294,6 @@ class RecentFavoriteTables
*/
public function getHtmlSyncFavoriteTables(): string
{
$retval = '';
if (Current::$server === 0) {
return '';
}
@ -305,16 +304,12 @@ class RecentFavoriteTables
$relationParameters->favoriteTablesFeature !== null
&& ! isset($_SESSION['tmpval']['favorites_synced'][Current::$server])
) {
$url = Url::getFromRoute('/database/structure/favorite-table', [
'ajax_request' => true,
'favorite_table' => true,
'sync_favorite_tables' => true,
]);
$retval = '<a class="hide" id="sync_favorite_tables"';
$retval .= ' href="' . $url . '"></a>';
$url = Url::getFromRoute('/sync-favorite-tables', ['ajax_request' => true]);
return '<a class="hide" id="sync_favorite_tables" href="' . $url . '"></a>';
}
return $retval;
return '';
}
/**

View File

@ -33,6 +33,7 @@ use PhpMyAdmin\Controllers\Preferences;
use PhpMyAdmin\Controllers\SchemaExportController;
use PhpMyAdmin\Controllers\Server;
use PhpMyAdmin\Controllers\Sql;
use PhpMyAdmin\Controllers\SyncFavoriteTablesController;
use PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Controllers\TableController;
use PhpMyAdmin\Controllers\ThemesController;
@ -314,5 +315,6 @@ final class Routes
$routes->addRoute(['GET', 'POST'], '/create', View\CreateController::class);
$routes->addRoute(['GET', 'POST'], '/operations', Operations\ViewController::class);
});
$routes->post('/sync-favorite-tables', SyncFavoriteTablesController::class);
}
}

View File

@ -1,64 +0,0 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Controllers\Database\Structure;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\Controllers\Database\Structure\FavoriteTableController;
use PhpMyAdmin\Current;
use PhpMyAdmin\Dbal\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Favorites\RecentFavoriteTables;
use PhpMyAdmin\Favorites\TableType;
use PhpMyAdmin\Template;
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Tests\Stubs\ResponseRenderer as ResponseStub;
use PHPUnit\Framework\Attributes\CoversClass;
use ReflectionMethod;
use ReflectionProperty;
use function json_encode;
#[CoversClass(FavoriteTableController::class)]
final class FavoriteTableControllerTest extends AbstractTestCase
{
public function testSynchronizeFavoriteTables(): void
{
$dbiDummy = $this->createDbiDummy();
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
$dbiDummy->addResult('SELECT 1 FROM `test_db`.`test_table` LIMIT 1;', [['1']], ['1']);
(new ReflectionProperty(RecentFavoriteTables::class, 'instances'))->setValue(null, []);
$favoriteInstance = RecentFavoriteTables::getInstance(TableType::Favorite);
$controller = new FavoriteTableController(
new ResponseStub(),
new Template(),
new Relation($dbi),
new DbTableExists($dbi),
);
// The user hash for test
$user = 'abcdefg';
$favoriteTable = [$user => [['db' => 'test_db', 'table' => 'test_table']]];
$_SESSION['tmpval'] = ['favorites_synced' => [Current::$server => null]];
$method = new ReflectionMethod(FavoriteTableController::class, 'synchronizeFavoriteTables');
$json = $method->invokeArgs($controller, [$favoriteInstance, $user, $favoriteTable]);
self::assertIsArray($json);
self::assertSame(json_encode($favoriteTable), $json['favoriteTables'] ?? '');
self::assertArrayHasKey('list', $json);
/**
* @psalm-suppress TypeDoesNotContainType
* @phpstan-ignore-next-line
*/
self::assertTrue($_SESSION['tmpval']['favorites_synced'][Current::$server]);
$dbiDummy->assertAllQueriesConsumed();
}
}

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Controllers;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\ConfigStorage\RelationParameters;
use PhpMyAdmin\Controllers\SyncFavoriteTablesController;
use PhpMyAdmin\Current;
use PhpMyAdmin\Dbal\DatabaseInterface;
use PhpMyAdmin\Favorites\RecentFavoriteTable;
use PhpMyAdmin\Favorites\RecentFavoriteTables;
use PhpMyAdmin\Http\Factory\ServerRequestFactory;
use PhpMyAdmin\Identifiers\DatabaseName;
use PhpMyAdmin\Identifiers\TableName;
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Tests\Stubs\ResponseRenderer as ResponseStub;
use PHPUnit\Framework\Attributes\CoversClass;
use ReflectionProperty;
use function json_decode;
use function json_encode;
#[CoversClass(SyncFavoriteTablesController::class)]
final class SyncFavoriteTablesControllerTest extends AbstractTestCase
{
public function testSynchronizeFavoriteTables(): void
{
$dbiDummy = $this->createDbiDummy();
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
(new ReflectionProperty(RecentFavoriteTables::class, 'instances'))->setValue(null, []);
$recentFavoriteTable = new RecentFavoriteTable(
DatabaseName::from('test_db'),
TableName::from('test_table'),
);
$dbiDummy->addResult('SELECT 1 FROM `test_db`.`test_table` LIMIT 1;', [['1']], ['1']);
$dbiDummy->addResult('SELECT `tables` FROM `pmadb`.`favorite` WHERE `username` = \'root\'', []);
$dbiDummy->addResult("REPLACE INTO `pmadb`.`favorite` (`username`, `tables`) VALUES ('root', "
. $dbi->quoteString(json_encode([$recentFavoriteTable])) .
')', true);
$relationParameters = RelationParameters::fromArray([
'db' => 'pmadb',
'favoritework' => 'favoritework',
'favorite' => 'favorite',
]);
(new ReflectionProperty(Relation::class, 'cache'))->setValue(null, $relationParameters);
$response = new ResponseStub();
$response->setAjax(true);
$controller = new SyncFavoriteTablesController(
$response,
new Relation($dbi),
);
// The user hash for test
$user = 'dc76e9f0c0006e8f919e0c515c66dbba3982f785';
$favoriteTable = json_encode([$user => [['db' => 'test_db', 'table' => 'test_table']]]);
$_SESSION['tmpval'] = ['favorites_synced' => [Current::$server => null]];
$request = ServerRequestFactory::create()->createServerRequest('POST', 'https://example.com/')
->withHeader('X-Requested-With', 'XMLHttpRequest')
->withParsedBody(['db' => 'test_db', 'favoriteTables' => $favoriteTable]);
$response = $controller($request);
$json = json_decode((string) $response->getBody(), true);
self::assertIsArray($json);
self::assertSame($favoriteTable, $json['favoriteTables'] ?? '');
self::assertArrayHasKey('list', $json);
/**
* @psalm-suppress TypeDoesNotContainType
* @phpstan-ignore-next-line
*/
self::assertTrue($_SESSION['tmpval']['favorites_synced'][Current::$server]);
$dbiDummy->assertAllQueriesConsumed();
}
}

View File

@ -36,6 +36,7 @@ use PhpMyAdmin\Controllers\Preferences;
use PhpMyAdmin\Controllers\SchemaExportController;
use PhpMyAdmin\Controllers\Server;
use PhpMyAdmin\Controllers\Sql;
use PhpMyAdmin\Controllers\SyncFavoriteTablesController;
use PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Controllers\TableController;
use PhpMyAdmin\Controllers\ThemesController;
@ -323,6 +324,7 @@ final class RoutesTest extends TestCase
'/version-check' => VersionCheckController::class,
'/view/create' => View\CreateController::class,
'/view/operations' => Operations\ViewController::class,
'/sync-favorite-tables' => SyncFavoriteTablesController::class,
];
$regex = '~^(?|/server/engines/([^/]+)|/server/engines/([^/]+)/([^/]+)|/server/variables/get/([^/]+)()())$~';