3 * Recent and Favorite table list handling
6 declare(strict_types
=1);
8 namespace PhpMyAdmin\Favorites
;
10 use PhpMyAdmin\Config
;
11 use PhpMyAdmin\ConfigStorage\Relation
;
12 use PhpMyAdmin\Current
;
13 use PhpMyAdmin\DatabaseInterface
;
14 use PhpMyAdmin\Dbal\ConnectionType
;
15 use PhpMyAdmin\DbTableExists
;
16 use PhpMyAdmin\Message
;
17 use PhpMyAdmin\Template
;
22 use function array_key_exists
;
23 use function array_pop
;
24 use function array_unique
;
25 use function array_unshift
;
26 use function array_values
;
28 use function in_array
;
29 use function is_string
;
30 use function json_decode
;
31 use function json_encode
;
36 use const SORT_REGULAR
;
39 * Handles the recently used and favorite tables.
41 * @TODO Change the release version in table pma_recent
42 * (#recent in documentation)
44 class RecentFavoriteTables
46 /** @var RecentFavoriteTable[] */
47 private array $tables = [];
50 * RecentFavoriteTable instances.
52 * @var array<string,RecentFavoriteTables>
54 private static array $instances = [];
56 /** @psalm-param int<0, max> $serverId */
57 private function __construct(
58 public Template
$template,
59 private readonly TableType
$tableType,
60 private readonly
int $serverId,
61 private readonly DatabaseInterface
$dbi,
62 private readonly Relation
$relation,
63 private readonly DbTableExists
$dbTableExists,
65 // Code search hint: recentTables
66 // Code search hint: favoriteTables
67 if (! isset($_SESSION['tmpval'][$this->tableType
->value
. 'Tables'][$this->serverId
])) {
68 $_SESSION['tmpval'][$this->tableType
->value
. 'Tables'][$this->serverId
] = $this->getPmaTable() !== null
73 foreach ($_SESSION['tmpval'][$this->tableType
->value
. 'Tables'][$this->serverId
] as $table) {
74 $this->tables
[] = RecentFavoriteTable
::fromArray($table);
78 public function __destruct()
80 $_SESSION['tmpval'][$this->tableType
->value
. 'Tables'][$this->serverId
] = [];
81 foreach ($this->tables
as $table) {
82 $_SESSION['tmpval'][$this->tableType
->value
. 'Tables'][$this->serverId
][] = $table->toArray();
87 * Returns class instance.
89 public static function getInstance(TableType
$type): RecentFavoriteTables
91 if (! array_key_exists($type->value
, self
::$instances)) {
92 $template = new Template();
93 $dbi = DatabaseInterface
::getInstance();
94 self
::$instances[$type->value
] = new RecentFavoriteTables(
100 new DbTableExists($dbi),
104 return self
::$instances[$type->value
];
108 * Returns the recent/favorite tables array
110 * @return RecentFavoriteTable[]
112 public function getTables(): array
114 return $this->tables
;
118 * Returns recently used tables or favorite from phpMyAdmin database.
120 * @return array{db:string, table:string}[]
122 private function getFromDb(): array
124 // Read from phpMyAdmin database, if recent tables is not in session
125 $sqlQuery = 'SELECT `tables` FROM ' . $this->getPmaTable()
126 . ' WHERE `username` = '
127 . $this->dbi
->quoteString(Config
::getInstance()->selectedServer
['user'], ConnectionType
::ControlUser
);
129 $result = $this->dbi
->tryQueryAsControlUser($sqlQuery);
130 if ($result !== false) {
131 $value = $result->fetchValue();
132 if (is_string($value)) {
133 return json_decode($value, true);
141 * Save recent/favorite tables into phpMyAdmin database.
143 * @return true|Message
145 private function saveToDb(): bool|Message
147 $username = Config
::getInstance()->selectedServer
['user'];
148 $sqlQuery = ' REPLACE INTO ' . $this->getPmaTable() . ' (`username`, `tables`)'
149 . ' VALUES (' . $this->dbi
->quoteString($username) . ', '
150 . $this->dbi
->quoteString(json_encode($this->tables
)) . ')';
152 $success = $this->dbi
->tryQuery($sqlQuery, ConnectionType
::ControlUser
);
154 if ($success === false) {
155 $message = Message
::error(match ($this->tableType
) {
156 TableType
::Recent
=> __('Could not save recent table!'),
157 TableType
::Favorite
=> __('Could not save favorite table!'),
160 $message->addMessage(
161 Message
::rawError($this->dbi
->getError(ConnectionType
::ControlUser
)),
172 * Trim recent/favorite table according to the
173 * NumRecentTables/NumFavoriteTables configuration.
175 private function trim(): void
178 Config
::getInstance()->settings
['Num' . ucfirst($this->tableType
->value
) . 'Tables'],
182 while (count($this->tables
) > $max) {
183 array_pop($this->tables
);
190 public function getHtmlList(): string
192 if ($this->tableType
=== TableType
::Recent
) {
194 foreach ($this->tables
as $table) {
195 $tables[] = $table->toArray();
198 return $this->template
->render('recent_favorite_table_recent', ['tables' => $tables]);
202 foreach ($this->tables
as $table) {
203 $removeParameters = [
204 'db' => $table->db
->getName(),
205 'favorite_table' => $table->table
->getName(),
206 'ajax_request' => true,
207 'remove_favorite' => true,
210 'db' => $table->db
->getName(),
211 'table' => $table->table
->getName(),
212 'md5' => md5($table->db
. '.' . $table->table
),
215 $tables[] = ['remove_parameters' => $removeParameters, 'table_parameters' => $tableParameters];
218 return $this->template
->render('recent_favorite_table_favorite', ['tables' => $tables]);
222 * Add recently used or favorite tables.
224 * @return true|Message True if success, Message if not
226 public function add(RecentFavoriteTable
$newTable): bool|Message
228 if (! $this->dbTableExists
->hasTable($newTable->db
, $newTable->table
)) {
232 // add only if this is new table
233 if (! isset($this->tables
[0]) ||
$this->tables
[0] != $newTable) {
234 array_unshift($this->tables
, $newTable);
235 $this->tables
= array_values(array_unique($this->tables
, SORT_REGULAR
));
237 if ($this->getPmaTable() !== null) {
238 return $this->saveToDb();
246 * Removes recent/favorite tables that don't exist.
248 * @return bool|Message True if invalid and removed, False if not invalid,
249 * Message if error while removing
251 public function removeIfInvalid(RecentFavoriteTable
$tableToRemove): bool|Message
253 foreach ($this->tables
as $table) {
255 $table->db
->getName() !== $tableToRemove->db
->getName()
256 ||
$table->table
->getName() !== $tableToRemove->table
->getName()
261 if (! $this->dbTableExists
->hasTable($table->db
, $table->table
)) {
262 return $this->remove($tableToRemove);
270 * Remove favorite tables.
272 * @return true|Message True if success, Message if not
274 public function remove(RecentFavoriteTable
$tableToRemove): bool|Message
276 foreach ($this->tables
as $key => $table) {
278 $table->db
->getName() !== $tableToRemove->db
->getName()
279 ||
$table->table
->getName() !== $tableToRemove->table
->getName()
284 unset($this->tables
[$key]);
287 if ($this->getPmaTable() !== null) {
288 return $this->saveToDb();
295 * Function to check if a table is already in favorite list.
297 public function contains(RecentFavoriteTable
$currentTable): bool
299 // When looking for the value we are looking for a similar object with
300 // the same public properties, not the same instance. The in_array must be loose comparison.
301 return in_array($currentTable, $this->tables
, false);
305 * Generate Html for sync Favorite tables anchor. (from localStorage to pmadb)
307 public function getHtmlSyncFavoriteTables(): string
310 if (Current
::$server === 0) {
314 $relationParameters = $this->relation
->getRelationParameters();
315 // Not to show this once list is synchronized.
317 $relationParameters->favoriteTablesFeature
!== null
318 && ! isset($_SESSION['tmpval']['favorites_synced'][Current
::$server])
320 $url = Url
::getFromRoute('/database/structure/favorite-table', [
321 'ajax_request' => true,
322 'favorite_table' => true,
323 'sync_favorite_tables' => true,
325 $retval = '<a class="hide" id="sync_favorite_tables"';
326 $retval .= ' href="' . $url . '"></a>';
333 * Return the name of the configuration storage table
335 * @return string|null pma table name
337 private function getPmaTable(): string|
null
339 $relationParameters = $this->relation
->getRelationParameters();
340 if ($this->tableType
=== TableType
::Recent
&& $relationParameters->recentlyUsedTablesFeature
!== null) {
341 return Util
::backquote($relationParameters->recentlyUsedTablesFeature
->database
)
342 . '.' . Util
::backquote($relationParameters->recentlyUsedTablesFeature
->recent
);
345 if ($this->tableType
=== TableType
::Favorite
&& $relationParameters->favoriteTablesFeature
!== null) {
346 return Util
::backquote($relationParameters->favoriteTablesFeature
->database
)
347 . '.' . Util
::backquote($relationParameters->favoriteTablesFeature
->favorite
);