RecentFavoriteTable.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. <?php
  2. /**
  3. * Recent and Favorite table list handling
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin;
  7. use PhpMyAdmin\Html\Generator;
  8. use const SORT_REGULAR;
  9. use function array_key_exists;
  10. use function array_merge;
  11. use function array_pop;
  12. use function array_unique;
  13. use function array_unshift;
  14. use function count;
  15. use function htmlspecialchars;
  16. use function json_decode;
  17. use function json_encode;
  18. use function max;
  19. use function md5;
  20. use function ucfirst;
  21. /**
  22. * Handles the recently used and favorite tables.
  23. *
  24. * @TODO Change the release version in table pma_recent
  25. * (#recent in documentation)
  26. */
  27. class RecentFavoriteTable
  28. {
  29. /**
  30. * Reference to session variable containing recently used or favorite tables.
  31. *
  32. * @access private
  33. * @var array
  34. */
  35. private $tables;
  36. /**
  37. * Defines type of action, Favorite or Recent table.
  38. *
  39. * @access private
  40. * @var string
  41. */
  42. private $tableType;
  43. /**
  44. * RecentFavoriteTable instances.
  45. *
  46. * @access private
  47. * @var array
  48. */
  49. private static $instances = [];
  50. /** @var Relation */
  51. private $relation;
  52. /**
  53. * Creates a new instance of RecentFavoriteTable
  54. *
  55. * @param string $type the table type
  56. *
  57. * @access private
  58. */
  59. private function __construct($type)
  60. {
  61. global $dbi;
  62. $this->relation = new Relation($dbi);
  63. $this->tableType = $type;
  64. $server_id = $GLOBALS['server'];
  65. if (! isset($_SESSION['tmpval'][$this->tableType . 'Tables'][$server_id])
  66. ) {
  67. $_SESSION['tmpval'][$this->tableType . 'Tables'][$server_id]
  68. = $this->getPmaTable() ? $this->getFromDb() : [];
  69. }
  70. $this->tables
  71. =& $_SESSION['tmpval'][$this->tableType . 'Tables'][$server_id];
  72. }
  73. /**
  74. * Returns class instance.
  75. *
  76. * @param string $type the table type
  77. *
  78. * @return RecentFavoriteTable
  79. */
  80. public static function getInstance($type)
  81. {
  82. if (! array_key_exists($type, self::$instances)) {
  83. self::$instances[$type] = new RecentFavoriteTable($type);
  84. }
  85. return self::$instances[$type];
  86. }
  87. /**
  88. * Returns the recent/favorite tables array
  89. *
  90. * @return array
  91. */
  92. public function getTables()
  93. {
  94. return $this->tables;
  95. }
  96. /**
  97. * Returns recently used tables or favorite from phpMyAdmin database.
  98. *
  99. * @return array
  100. */
  101. public function getFromDb()
  102. {
  103. global $dbi;
  104. // Read from phpMyAdmin database, if recent tables is not in session
  105. $sql_query
  106. = ' SELECT `tables` FROM ' . $this->getPmaTable() .
  107. " WHERE `username` = '" . $dbi->escapeString($GLOBALS['cfg']['Server']['user']) . "'";
  108. $return = [];
  109. $result = $this->relation->queryAsControlUser($sql_query, false);
  110. if ($result) {
  111. $row = $dbi->fetchArray($result);
  112. if (isset($row[0])) {
  113. $return = json_decode($row[0], true);
  114. }
  115. }
  116. return $return;
  117. }
  118. /**
  119. * Save recent/favorite tables into phpMyAdmin database.
  120. *
  121. * @return true|Message
  122. */
  123. public function saveToDb()
  124. {
  125. global $dbi;
  126. $username = $GLOBALS['cfg']['Server']['user'];
  127. $sql_query
  128. = ' REPLACE INTO ' . $this->getPmaTable() . ' (`username`, `tables`)' .
  129. " VALUES ('" . $dbi->escapeString($username) . "', '"
  130. . $dbi->escapeString(
  131. json_encode($this->tables)
  132. ) . "')";
  133. $success = $dbi->tryQuery($sql_query, DatabaseInterface::CONNECT_CONTROL);
  134. if (! $success) {
  135. $error_msg = '';
  136. switch ($this->tableType) {
  137. case 'recent':
  138. $error_msg = __('Could not save recent table!');
  139. break;
  140. case 'favorite':
  141. $error_msg = __('Could not save favorite table!');
  142. break;
  143. }
  144. $message = Message::error($error_msg);
  145. $message->addMessage(
  146. Message::rawError(
  147. $dbi->getError(DatabaseInterface::CONNECT_CONTROL)
  148. ),
  149. '<br><br>'
  150. );
  151. return $message;
  152. }
  153. return true;
  154. }
  155. /**
  156. * Trim recent.favorite table according to the
  157. * NumRecentTables/NumFavoriteTables configuration.
  158. *
  159. * @return bool True if trimming occurred
  160. */
  161. public function trim()
  162. {
  163. $max = max(
  164. $GLOBALS['cfg']['Num' . ucfirst($this->tableType) . 'Tables'],
  165. 0
  166. );
  167. $trimming_occurred = count($this->tables) > $max;
  168. while (count($this->tables) > $max) {
  169. array_pop($this->tables);
  170. }
  171. return $trimming_occurred;
  172. }
  173. /**
  174. * Return HTML ul.
  175. *
  176. * @return string
  177. */
  178. public function getHtmlList()
  179. {
  180. $html = '';
  181. if (count($this->tables)) {
  182. if ($this->tableType === 'recent') {
  183. foreach ($this->tables as $table) {
  184. $html .= '<li class="warp_link">';
  185. $recent_url = Url::getFromRoute('/table/recent-favorite', [
  186. 'db' => $table['db'],
  187. 'table' => $table['table'],
  188. ]);
  189. $html .= '<a href="' . $recent_url . '">`'
  190. . htmlspecialchars($table['db']) . '`.`'
  191. . htmlspecialchars($table['table']) . '`</a>';
  192. $html .= '</li>';
  193. }
  194. } else {
  195. foreach ($this->tables as $table) {
  196. $html .= '<li class="warp_link">';
  197. $html .= '<a class="ajax favorite_table_anchor" ';
  198. $fav_rm_url = Url::getFromRoute('/database/structure/favorite-table', [
  199. 'db' => $table['db'],
  200. 'ajax_request' => true,
  201. 'favorite_table' => $table['table'],
  202. 'remove_favorite' => true,
  203. ]);
  204. $html .= 'href="' . $fav_rm_url
  205. . '" title="' . __('Remove from Favorites')
  206. . '" data-favtargetn="'
  207. . md5($table['db'] . '.' . $table['table'])
  208. . '" >'
  209. . Generator::getIcon('b_favorite')
  210. . '</a>';
  211. $table_url = Url::getFromRoute('/table/recent-favorite', [
  212. 'db' => $table['db'],
  213. 'table' => $table['table'],
  214. ]);
  215. $html .= '<a href="' . $table_url . '">`'
  216. . htmlspecialchars($table['db']) . '`.`'
  217. . htmlspecialchars($table['table']) . '`</a>';
  218. $html .= '</li>';
  219. }
  220. }
  221. } else {
  222. $html .= '<li class="warp_link">'
  223. . ($this->tableType === 'recent'
  224. ? __('There are no recent tables.')
  225. : __('There are no favorite tables.'))
  226. . '</li>';
  227. }
  228. return $html;
  229. }
  230. /**
  231. * Return HTML.
  232. *
  233. * @return string
  234. */
  235. public function getHtml()
  236. {
  237. $html = '<div class="drop_list">';
  238. if ($this->tableType === 'recent') {
  239. $html .= '<button title="' . __('Recent tables')
  240. . '" class="drop_button btn">'
  241. . __('Recent') . '</button><ul id="pma_recent_list">';
  242. } else {
  243. $html .= '<button title="' . __('Favorite tables')
  244. . '" class="drop_button btn">'
  245. . __('Favorites') . '</button><ul id="pma_favorite_list">';
  246. }
  247. $html .= $this->getHtmlList();
  248. $html .= '</ul></div>';
  249. return $html;
  250. }
  251. /**
  252. * Add recently used or favorite tables.
  253. *
  254. * @param string $db database name where the table is located
  255. * @param string $table table name
  256. *
  257. * @return true|Message True if success, Message if not
  258. */
  259. public function add($db, $table)
  260. {
  261. global $dbi;
  262. // If table does not exist, do not add._getPmaTable()
  263. if (! $dbi->getColumns($db, $table)) {
  264. return true;
  265. }
  266. $table_arr = [];
  267. $table_arr['db'] = $db;
  268. $table_arr['table'] = $table;
  269. // add only if this is new table
  270. if (! isset($this->tables[0]) || $this->tables[0] != $table_arr) {
  271. array_unshift($this->tables, $table_arr);
  272. $this->tables = array_merge(array_unique($this->tables, SORT_REGULAR));
  273. $this->trim();
  274. if ($this->getPmaTable()) {
  275. return $this->saveToDb();
  276. }
  277. }
  278. return true;
  279. }
  280. /**
  281. * Removes recent/favorite tables that don't exist.
  282. *
  283. * @param string $db database
  284. * @param string $table table
  285. *
  286. * @return bool|Message True if invalid and removed, False if not invalid,
  287. * Message if error while removing
  288. */
  289. public function removeIfInvalid($db, $table)
  290. {
  291. global $dbi;
  292. foreach ($this->tables as $tbl) {
  293. if ($tbl['db'] != $db || $tbl['table'] != $table) {
  294. continue;
  295. }
  296. // TODO Figure out a better way to find the existence of a table
  297. if (! $dbi->getColumns($tbl['db'], $tbl['table'])) {
  298. return $this->remove($tbl['db'], $tbl['table']);
  299. }
  300. }
  301. return false;
  302. }
  303. /**
  304. * Remove favorite tables.
  305. *
  306. * @param string $db database name where the table is located
  307. * @param string $table table name
  308. *
  309. * @return true|Message True if success, Message if not
  310. */
  311. public function remove($db, $table)
  312. {
  313. foreach ($this->tables as $key => $value) {
  314. if ($value['db'] != $db || $value['table'] != $table) {
  315. continue;
  316. }
  317. unset($this->tables[$key]);
  318. }
  319. if ($this->getPmaTable()) {
  320. return $this->saveToDb();
  321. }
  322. return true;
  323. }
  324. /**
  325. * Generate Html for sync Favorite tables anchor. (from localStorage to pmadb)
  326. *
  327. * @return string
  328. */
  329. public function getHtmlSyncFavoriteTables()
  330. {
  331. $retval = '';
  332. $server_id = $GLOBALS['server'];
  333. if ($server_id == 0) {
  334. return '';
  335. }
  336. $cfgRelation = $this->relation->getRelationsParam();
  337. // Not to show this once list is synchronized.
  338. if ($cfgRelation['favoritework'] && ! isset($_SESSION['tmpval']['favorites_synced'][$server_id])) {
  339. $url = Url::getFromRoute('/database/structure/favorite-table', [
  340. 'ajax_request' => true,
  341. 'favorite_table' => true,
  342. 'sync_favorite_tables' => true,
  343. ]);
  344. $retval = '<a class="hide" id="sync_favorite_tables"';
  345. $retval .= ' href="' . $url . '"></a>';
  346. }
  347. return $retval;
  348. }
  349. /**
  350. * Generate Html to update recent tables.
  351. *
  352. * @return string html
  353. */
  354. public static function getHtmlUpdateRecentTables()
  355. {
  356. $retval = '<a class="hide" id="update_recent_tables" href="';
  357. $retval .= Url::getFromRoute('/recent-table', [
  358. 'ajax_request' => true,
  359. 'recent_table' => true,
  360. ]);
  361. $retval .= '"></a>';
  362. return $retval;
  363. }
  364. /**
  365. * Return the name of the configuration storage table
  366. *
  367. * @return string|null pma table name
  368. */
  369. private function getPmaTable(): ?string
  370. {
  371. $cfgRelation = $this->relation->getRelationsParam();
  372. if (! $cfgRelation['recentwork']) {
  373. return null;
  374. }
  375. if (! empty($cfgRelation['db'])
  376. && ! empty($cfgRelation[$this->tableType])
  377. ) {
  378. return Util::backquote($cfgRelation['db']) . '.'
  379. . Util::backquote($cfgRelation[$this->tableType]);
  380. }
  381. return null;
  382. }
  383. }