RecentFavoriteTable.php 12 KB

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