3 * Recent and Favorite table list handling
6 declare(strict_types
=1);
10 use PhpMyAdmin\ConfigStorage\Relation
;
11 use PhpMyAdmin\Dbal\Connection
;
14 use function array_key_exists
;
15 use function array_merge
;
16 use function array_pop
;
17 use function array_unique
;
18 use function array_unshift
;
20 use function is_string
;
21 use function json_decode
;
22 use function json_encode
;
27 use const SORT_REGULAR
;
30 * Handles the recently used and favorite tables.
32 * @TODO Change the release version in table pma_recent
33 * (#recent in documentation)
35 class RecentFavoriteTable
38 * Reference to session variable containing recently used or favorite tables.
42 private array $tables = [];
45 * RecentFavoriteTable instances.
47 * @var array<string,RecentFavoriteTable>
49 private static array $instances = [];
51 private Relation
$relation;
54 * Creates a new instance of RecentFavoriteTable
56 * @param string $tableType Defines type of action, Favorite or Recent table.
57 * @phpstan-param 'favorite'|'recent' $tableType
59 private function __construct(public Template
$template, private string $tableType)
61 $this->relation
= new Relation(DatabaseInterface
::getInstance());
62 $serverId = $GLOBALS['server'];
63 // Code search hint: recentTables
64 // Code search hint: favoriteTables
65 if (! isset($_SESSION['tmpval'][$this->tableType
. 'Tables'][$serverId])) {
66 $_SESSION['tmpval'][$this->tableType
. 'Tables'][$serverId] = $this->getPmaTable()
71 $this->tables
=& $_SESSION['tmpval'][$this->tableType
. 'Tables'][$serverId];
75 * Returns class instance.
77 * @param string $type the table type
78 * @psalm-param 'favorite'|'recent' $type
80 public static function getInstance(string $type): RecentFavoriteTable
82 if (! array_key_exists($type, self
::$instances)) {
83 $template = new Template();
84 self
::$instances[$type] = new RecentFavoriteTable($template, $type);
87 return self
::$instances[$type];
91 * Returns the recent/favorite tables array
95 public function getTables(): array
101 * Returns recently used tables or favorite from phpMyAdmin database.
105 public function getFromDb(): array
107 // Read from phpMyAdmin database, if recent tables is not in session
108 $dbi = DatabaseInterface
::getInstance();
109 $sqlQuery = ' SELECT `tables` FROM ' . $this->getPmaTable()
110 . ' WHERE `username` = '
111 . $dbi->quoteString(Config
::getInstance()->selectedServer
['user'], Connection
::TYPE_CONTROL
);
113 $result = $dbi->tryQueryAsControlUser($sqlQuery);
115 $value = $result->fetchValue();
116 if (is_string($value)) {
117 return json_decode($value, true);
125 * Save recent/favorite tables into phpMyAdmin database.
127 * @return true|Message
129 public function saveToDb(): bool|Message
131 $username = Config
::getInstance()->selectedServer
['user'];
132 $dbi = DatabaseInterface
::getInstance();
133 $sqlQuery = ' REPLACE INTO ' . $this->getPmaTable() . ' (`username`, `tables`)'
134 . ' VALUES (' . $dbi->quoteString($username) . ', '
135 . $dbi->quoteString(json_encode($this->tables
)) . ')';
137 $success = $dbi->tryQuery($sqlQuery, Connection
::TYPE_CONTROL
);
140 $errorMsg = match ($this->tableType
) {
141 'recent' => __('Could not save recent table!'),
142 'favorite' => __('Could not save favorite table!'),
145 $message = Message
::error($errorMsg);
146 $message->addMessage(
147 Message
::rawError($dbi->getError(Connection
::TYPE_CONTROL
)),
158 * Trim recent.favorite table according to the
159 * NumRecentTables/NumFavoriteTables configuration.
161 public function trim(): bool
164 Config
::getInstance()->settings
['Num' . ucfirst($this->tableType
) . 'Tables'],
167 $trimmingOccurred = count($this->tables
) > $max;
168 while (count($this->tables
) > $max) {
169 array_pop($this->tables
);
172 return $trimmingOccurred;
178 public function getHtmlList(): string
180 if ($this->tables
!== []) {
181 if ($this->tableType
=== 'recent') {
183 foreach ($this->tables
as $table) {
184 $tables[] = ['db' => $table['db'], 'table' => $table['table']];
187 return $this->template
->render('recent_favorite_table_recent', ['tables' => $tables]);
191 foreach ($this->tables
as $table) {
192 $removeParameters = [
193 'db' => $table['db'],
194 'ajax_request' => true,
195 'favorite_table' => $table['table'],
196 'remove_favorite' => true,
199 'db' => $table['db'],
200 'table' => $table['table'],
201 'md5' => md5($table['db'] . '.' . $table['table']),
204 $tables[] = ['remove_parameters' => $removeParameters, 'table_parameters' => $tableParameters];
207 return $this->template
->render('recent_favorite_table_favorite', ['tables' => $tables]);
210 return $this->template
->render('recent_favorite_table_no_tables', [
211 'is_recent' => $this->tableType
=== 'recent',
215 public function getHtml(): string
217 $html = '<div class="drop_list">';
218 if ($this->tableType
=== 'recent') {
219 $html .= '<button title="' . __('Recent tables')
220 . '" class="drop_button btn btn-sm btn-outline-secondary">'
221 . __('Recent') . '</button><ul id="pma_recent_list">';
223 $html .= '<button title="' . __('Favorite tables')
224 . '" class="drop_button btn btn-sm btn-outline-secondary">'
225 . __('Favorites') . '</button><ul id="pma_favorite_list">';
228 $html .= $this->getHtmlList();
229 $html .= '</ul></div>';
235 * Add recently used or favorite tables.
237 * @param string $db database name where the table is located
238 * @param string $table table name
240 * @return true|Message True if success, Message if not
242 public function add(string $db, string $table): bool|Message
244 // If table does not exist, do not add._getPmaTable()
245 if (DatabaseInterface
::getInstance()->getColumns($db, $table) === []) {
250 $tableArr['db'] = $db;
251 $tableArr['table'] = $table;
253 // add only if this is new table
254 if (! isset($this->tables
[0]) ||
$this->tables
[0] != $tableArr) {
255 array_unshift($this->tables
, $tableArr);
256 $this->tables
= array_merge(array_unique($this->tables
, SORT_REGULAR
));
258 if ($this->getPmaTable()) {
259 return $this->saveToDb();
267 * Removes recent/favorite tables that don't exist.
269 * @param string $db database
270 * @param string $table table
272 * @return bool|Message True if invalid and removed, False if not invalid,
273 * Message if error while removing
275 public function removeIfInvalid(string $db, string $table): bool|Message
277 foreach ($this->tables
as $tbl) {
278 if ($tbl['db'] != $db ||
$tbl['table'] != $table) {
282 // TODO Figure out a better way to find the existence of a table
283 if (DatabaseInterface
::getInstance()->getColumns($tbl['db'], $tbl['table']) === []) {
284 return $this->remove($tbl['db'], $tbl['table']);
292 * Remove favorite tables.
294 * @param string $db database name where the table is located
295 * @param string $table table name
297 * @return true|Message True if success, Message if not
299 public function remove(string $db, string $table): bool|Message
301 foreach ($this->tables
as $key => $value) {
302 if ($value['db'] != $db ||
$value['table'] != $table) {
306 unset($this->tables
[$key]);
309 if ($this->getPmaTable()) {
310 return $this->saveToDb();
317 * Generate Html for sync Favorite tables anchor. (from localStorage to pmadb)
319 public function getHtmlSyncFavoriteTables(): string
322 $serverId = $GLOBALS['server'];
323 if ($serverId == 0) {
327 $relationParameters = $this->relation
->getRelationParameters();
328 // Not to show this once list is synchronized.
330 $relationParameters->favoriteTablesFeature
!== null
331 && ! isset($_SESSION['tmpval']['favorites_synced'][$serverId])
333 $url = Url
::getFromRoute('/database/structure/favorite-table', [
334 'ajax_request' => true,
335 'favorite_table' => true,
336 'sync_favorite_tables' => true,
338 $retval = '<a class="hide" id="sync_favorite_tables"';
339 $retval .= ' href="' . $url . '"></a>';
346 * Generate Html to update recent tables.
348 public static function getHtmlUpdateRecentTables(): string
350 return '<a class="hide" id="update_recent_tables" href="'
351 . Url
::getFromRoute('/recent-table', ['ajax_request' => true, 'recent_table' => true])
356 * Return the name of the configuration storage table
358 * @return string|null pma table name
360 private function getPmaTable(): string|
null
362 $relationParameters = $this->relation
->getRelationParameters();
363 if ($this->tableType
=== 'recent' && $relationParameters->recentlyUsedTablesFeature
!== null) {
364 return Util
::backquote($relationParameters->recentlyUsedTablesFeature
->database
)
365 . '.' . Util
::backquote($relationParameters->recentlyUsedTablesFeature
->recent
);
368 if ($this->tableType
=== 'favorite' && $relationParameters->favoriteTablesFeature
!== null) {
369 return Util
::backquote($relationParameters->favoriteTablesFeature
->database
)
370 . '.' . Util
::backquote($relationParameters->favoriteTablesFeature
->favorite
);