Header.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Used to render the header of PMA's pages
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. declare(strict_types=1);
  9. namespace PhpMyAdmin;
  10. use PhpMyAdmin\Navigation\Navigation;
  11. /**
  12. * Class used to output the HTTP and HTML headers
  13. *
  14. * @package PhpMyAdmin
  15. */
  16. class Header
  17. {
  18. /**
  19. * Scripts instance
  20. *
  21. * @access private
  22. * @var Scripts
  23. */
  24. private $_scripts;
  25. /**
  26. * PhpMyAdmin\Console instance
  27. *
  28. * @access private
  29. * @var Console
  30. */
  31. private $_console;
  32. /**
  33. * Menu instance
  34. *
  35. * @access private
  36. * @var Menu
  37. */
  38. private $_menu;
  39. /**
  40. * Whether to offer the option of importing user settings
  41. *
  42. * @access private
  43. * @var bool
  44. */
  45. private $_userprefsOfferImport;
  46. /**
  47. * The page title
  48. *
  49. * @access private
  50. * @var string
  51. */
  52. private $_title;
  53. /**
  54. * The value for the id attribute for the body tag
  55. *
  56. * @access private
  57. * @var string
  58. */
  59. private $_bodyId;
  60. /**
  61. * Whether to show the top menu
  62. *
  63. * @access private
  64. * @var bool
  65. */
  66. private $_menuEnabled;
  67. /**
  68. * Whether to show the warnings
  69. *
  70. * @access private
  71. * @var bool
  72. */
  73. private $_warningsEnabled;
  74. /**
  75. * Whether the page is in 'print view' mode
  76. *
  77. * @access private
  78. * @var bool
  79. */
  80. private $_isPrintView;
  81. /**
  82. * Whether we are servicing an ajax request.
  83. *
  84. * @access private
  85. * @var bool
  86. */
  87. private $_isAjax;
  88. /**
  89. * Whether to display anything
  90. *
  91. * @access private
  92. * @var bool
  93. */
  94. private $_isEnabled;
  95. /**
  96. * Whether the HTTP headers (and possibly some HTML)
  97. * have already been sent to the browser
  98. *
  99. * @access private
  100. * @var bool
  101. */
  102. private $_headerIsSent;
  103. /**
  104. * @var UserPreferences
  105. */
  106. private $userPreferences;
  107. /**
  108. * @var Template
  109. */
  110. private $template;
  111. /**
  112. * Creates a new class instance
  113. */
  114. public function __construct()
  115. {
  116. $this->template = new Template();
  117. $this->_isEnabled = true;
  118. $this->_isAjax = false;
  119. $this->_bodyId = '';
  120. $this->_title = '';
  121. $this->_console = new Console();
  122. $db = strlen($GLOBALS['db']) ? $GLOBALS['db'] : '';
  123. $table = strlen($GLOBALS['table']) ? $GLOBALS['table'] : '';
  124. $this->_menu = new Menu(
  125. $db,
  126. $table
  127. );
  128. $this->_menuEnabled = true;
  129. $this->_warningsEnabled = true;
  130. $this->_isPrintView = false;
  131. $this->_scripts = new Scripts();
  132. $this->_addDefaultScripts();
  133. $this->_headerIsSent = false;
  134. // if database storage for user preferences is transient,
  135. // offer to load exported settings from localStorage
  136. // (detection will be done in JavaScript)
  137. $this->_userprefsOfferImport = false;
  138. if ($GLOBALS['PMA_Config']->get('user_preferences') == 'session'
  139. && ! isset($_SESSION['userprefs_autoload'])
  140. ) {
  141. $this->_userprefsOfferImport = true;
  142. }
  143. $this->userPreferences = new UserPreferences();
  144. }
  145. /**
  146. * Loads common scripts
  147. *
  148. * @return void
  149. */
  150. private function _addDefaultScripts(): void
  151. {
  152. // Localised strings
  153. $this->_scripts->addFile('vendor/jquery/jquery.min.js');
  154. $this->_scripts->addFile('vendor/jquery/jquery-migrate.js');
  155. $this->_scripts->addFile('whitelist.php');
  156. $this->_scripts->addFile('vendor/sprintf.js');
  157. $this->_scripts->addFile('ajax.js');
  158. $this->_scripts->addFile('keyhandler.js');
  159. $this->_scripts->addFile('vendor/bootstrap/bootstrap.bundle.min.js');
  160. $this->_scripts->addFile('vendor/jquery/jquery-ui.min.js');
  161. $this->_scripts->addFile('vendor/js.cookie.js');
  162. $this->_scripts->addFile('vendor/jquery/jquery.mousewheel.js');
  163. $this->_scripts->addFile('vendor/jquery/jquery.event.drag-2.2.js');
  164. $this->_scripts->addFile('vendor/jquery/jquery.validate.js');
  165. $this->_scripts->addFile('vendor/jquery/jquery-ui-timepicker-addon.js');
  166. $this->_scripts->addFile('vendor/jquery/jquery.ba-hashchange-1.3.js');
  167. $this->_scripts->addFile('vendor/jquery/jquery.debounce-1.0.5.js');
  168. $this->_scripts->addFile('menu_resizer.js');
  169. // Cross-framing protection
  170. if ($GLOBALS['cfg']['AllowThirdPartyFraming'] === false) {
  171. $this->_scripts->addFile('cross_framing_protection.js');
  172. }
  173. $this->_scripts->addFile('rte.js');
  174. if ($GLOBALS['cfg']['SendErrorReports'] !== 'never') {
  175. $this->_scripts->addFile('vendor/tracekit.js');
  176. $this->_scripts->addFile('error_report.js');
  177. }
  178. // Here would not be a good place to add CodeMirror because
  179. // the user preferences have not been merged at this point
  180. $this->_scripts->addFile('messages.php', ['l' => $GLOBALS['lang']]);
  181. $this->_scripts->addFile('config.js');
  182. $this->_scripts->addFile('doclinks.js');
  183. $this->_scripts->addFile('functions.js');
  184. $this->_scripts->addFile('navigation.js');
  185. $this->_scripts->addFile('indexes.js');
  186. $this->_scripts->addFile('common.js');
  187. $this->_scripts->addFile('page_settings.js');
  188. if ($GLOBALS['cfg']['enable_drag_drop_import'] === true) {
  189. $this->_scripts->addFile('drag_drop_import.js');
  190. }
  191. if (! $GLOBALS['PMA_Config']->get('DisableShortcutKeys')) {
  192. $this->_scripts->addFile('shortcuts_handler.js');
  193. }
  194. $this->_scripts->addCode($this->getJsParamsCode());
  195. }
  196. /**
  197. * Returns, as an array, a list of parameters
  198. * used on the client side
  199. *
  200. * @return array
  201. */
  202. public function getJsParams(): array
  203. {
  204. $db = strlen($GLOBALS['db']) ? $GLOBALS['db'] : '';
  205. $table = strlen($GLOBALS['table']) ? $GLOBALS['table'] : '';
  206. $pftext = isset($_SESSION['tmpval']['pftext'])
  207. ? $_SESSION['tmpval']['pftext'] : '';
  208. $params = [
  209. 'common_query' => Url::getCommonRaw(),
  210. 'opendb_url' => Util::getScriptNameForOption(
  211. $GLOBALS['cfg']['DefaultTabDatabase'],
  212. 'database'
  213. ),
  214. 'lang' => $GLOBALS['lang'],
  215. 'server' => $GLOBALS['server'],
  216. 'table' => $table,
  217. 'db' => $db,
  218. 'token' => $_SESSION[' PMA_token '],
  219. 'text_dir' => $GLOBALS['text_dir'],
  220. 'show_databases_navigation_as_tree' => $GLOBALS['cfg']['ShowDatabasesNavigationAsTree'],
  221. 'pma_text_default_tab' => Util::getTitleForTarget(
  222. $GLOBALS['cfg']['DefaultTabTable']
  223. ),
  224. 'pma_text_left_default_tab' => Util::getTitleForTarget(
  225. $GLOBALS['cfg']['NavigationTreeDefaultTabTable']
  226. ),
  227. 'pma_text_left_default_tab2' => Util::getTitleForTarget(
  228. $GLOBALS['cfg']['NavigationTreeDefaultTabTable2']
  229. ),
  230. 'LimitChars' => $GLOBALS['cfg']['LimitChars'],
  231. 'pftext' => $pftext,
  232. 'confirm' => $GLOBALS['cfg']['Confirm'],
  233. 'LoginCookieValidity' => $GLOBALS['cfg']['LoginCookieValidity'],
  234. 'session_gc_maxlifetime' => (int) ini_get('session.gc_maxlifetime'),
  235. 'logged_in' => isset($GLOBALS['dbi']) ? $GLOBALS['dbi']->isUserType('logged') : false,
  236. 'is_https' => $GLOBALS['PMA_Config']->isHttps(),
  237. 'rootPath' => $GLOBALS['PMA_Config']->getRootPath(),
  238. 'arg_separator' => Url::getArgSeparator(),
  239. 'PMA_VERSION' => PMA_VERSION,
  240. ];
  241. if (isset($GLOBALS['cfg']['Server'])
  242. && isset($GLOBALS['cfg']['Server']['auth_type'])
  243. ) {
  244. $params['auth_type'] = $GLOBALS['cfg']['Server']['auth_type'];
  245. if (isset($GLOBALS['cfg']['Server']['user'])) {
  246. $params['user'] = $GLOBALS['cfg']['Server']['user'];
  247. }
  248. }
  249. return $params;
  250. }
  251. /**
  252. * Returns, as a string, a list of parameters
  253. * used on the client side
  254. *
  255. * @return string
  256. */
  257. public function getJsParamsCode(): string
  258. {
  259. $params = $this->getJsParams();
  260. foreach ($params as $key => $value) {
  261. if (is_bool($value)) {
  262. $params[$key] = $key . ':' . ($value ? 'true' : 'false') . '';
  263. } else {
  264. $params[$key] = $key . ':"' . Sanitize::escapeJsString($value) . '"';
  265. }
  266. }
  267. return 'CommonParams.setAll({' . implode(',', $params) . '});';
  268. }
  269. /**
  270. * Disables the rendering of the header
  271. *
  272. * @return void
  273. */
  274. public function disable(): void
  275. {
  276. $this->_isEnabled = false;
  277. }
  278. /**
  279. * Set the ajax flag to indicate whether
  280. * we are servicing an ajax request
  281. *
  282. * @param bool $isAjax Whether we are servicing an ajax request
  283. *
  284. * @return void
  285. */
  286. public function setAjax(bool $isAjax): void
  287. {
  288. $this->_isAjax = $isAjax;
  289. $this->_console->setAjax($isAjax);
  290. }
  291. /**
  292. * Returns the Scripts object
  293. *
  294. * @return Scripts object
  295. */
  296. public function getScripts(): Scripts
  297. {
  298. return $this->_scripts;
  299. }
  300. /**
  301. * Returns the Menu object
  302. *
  303. * @return Menu object
  304. */
  305. public function getMenu(): Menu
  306. {
  307. return $this->_menu;
  308. }
  309. /**
  310. * Setter for the ID attribute in the BODY tag
  311. *
  312. * @param string $id Value for the ID attribute
  313. *
  314. * @return void
  315. */
  316. public function setBodyId(string $id): void
  317. {
  318. $this->_bodyId = htmlspecialchars($id);
  319. }
  320. /**
  321. * Setter for the title of the page
  322. *
  323. * @param string $title New title
  324. *
  325. * @return void
  326. */
  327. public function setTitle(string $title): void
  328. {
  329. $this->_title = htmlspecialchars($title);
  330. }
  331. /**
  332. * Disables the display of the top menu
  333. *
  334. * @return void
  335. */
  336. public function disableMenuAndConsole(): void
  337. {
  338. $this->_menuEnabled = false;
  339. $this->_console->disable();
  340. }
  341. /**
  342. * Disables the display of the top menu
  343. *
  344. * @return void
  345. */
  346. public function disableWarnings(): void
  347. {
  348. $this->_warningsEnabled = false;
  349. }
  350. /**
  351. * Turns on 'print view' mode
  352. *
  353. * @return void
  354. */
  355. public function enablePrintView(): void
  356. {
  357. $this->disableMenuAndConsole();
  358. $this->setTitle(__('Print view') . ' - phpMyAdmin ' . PMA_VERSION);
  359. $this->_isPrintView = true;
  360. }
  361. /**
  362. * Generates the header
  363. *
  364. * @return string The header
  365. */
  366. public function getDisplay(): string
  367. {
  368. if (! $this->_headerIsSent && $this->_isEnabled) {
  369. if (! $this->_isAjax) {
  370. $this->sendHttpHeaders();
  371. $baseDir = defined('PMA_PATH_TO_BASEDIR') ? PMA_PATH_TO_BASEDIR : '';
  372. $uniqueValue = $GLOBALS['PMA_Config']->getThemeUniqueValue();
  373. $themePath = $GLOBALS['pmaThemePath'];
  374. $version = self::getVersionParameter();
  375. // The user preferences have been merged at this point
  376. // so we can conditionally add CodeMirror
  377. if ($GLOBALS['cfg']['CodemirrorEnable']) {
  378. $this->_scripts->addFile('vendor/codemirror/lib/codemirror.js');
  379. $this->_scripts->addFile('vendor/codemirror/mode/sql/sql.js');
  380. $this->_scripts->addFile('vendor/codemirror/addon/runmode/runmode.js');
  381. $this->_scripts->addFile('vendor/codemirror/addon/hint/show-hint.js');
  382. $this->_scripts->addFile('vendor/codemirror/addon/hint/sql-hint.js');
  383. if ($GLOBALS['cfg']['LintEnable']) {
  384. $this->_scripts->addFile('vendor/codemirror/addon/lint/lint.js');
  385. $this->_scripts->addFile(
  386. 'codemirror/addon/lint/sql-lint.js'
  387. );
  388. }
  389. }
  390. $this->_scripts->addCode(
  391. 'ConsoleEnterExecutes='
  392. . ($GLOBALS['cfg']['ConsoleEnterExecutes'] ? 'true' : 'false')
  393. );
  394. $this->_scripts->addFiles($this->_console->getScripts());
  395. if ($this->_userprefsOfferImport) {
  396. $this->_scripts->addFile('config.js');
  397. }
  398. if ($this->_menuEnabled && $GLOBALS['server'] > 0) {
  399. $nav = new Navigation(
  400. $this->template,
  401. new Relation($GLOBALS['dbi']),
  402. $GLOBALS['dbi']
  403. );
  404. $navigation = $nav->getDisplay();
  405. }
  406. $customHeader = Config::renderHeader();
  407. // offer to load user preferences from localStorage
  408. if ($this->_userprefsOfferImport) {
  409. $loadUserPreferences = $this->userPreferences->autoloadGetHeader();
  410. }
  411. if ($this->_menuEnabled && $GLOBALS['server'] > 0) {
  412. $menu = $this->_menu->getDisplay();
  413. }
  414. $console = $this->_console->getDisplay();
  415. $messages = $this->getMessage();
  416. }
  417. if (empty($_REQUEST['recent_table'])) {
  418. $recentTable = $this->_addRecentTable(
  419. $GLOBALS['db'],
  420. $GLOBALS['table']
  421. );
  422. }
  423. return $this->template->render('header', [
  424. 'is_ajax' => $this->_isAjax,
  425. 'is_enabled' => $this->_isEnabled,
  426. 'lang' => $GLOBALS['lang'],
  427. 'allow_third_party_framing' => $GLOBALS['cfg']['AllowThirdPartyFraming'],
  428. 'is_print_view' => $this->_isPrintView,
  429. 'base_dir' => $baseDir ?? '',
  430. 'unique_value' => $uniqueValue ?? '',
  431. 'theme_path' => $themePath ?? '',
  432. 'version' => $version ?? '',
  433. 'text_dir' => $GLOBALS['text_dir'],
  434. 'server' => $GLOBALS['server'] ?? null,
  435. 'title' => $this->getPageTitle(),
  436. 'scripts' => $this->_scripts->getDisplay(),
  437. 'body_id' => $this->_bodyId,
  438. 'navigation' => $navigation ?? '',
  439. 'custom_header' => $customHeader ?? '',
  440. 'load_user_preferences' => $loadUserPreferences ?? '',
  441. 'show_hint' => $GLOBALS['cfg']['ShowHint'],
  442. 'is_warnings_enabled' => $this->_warningsEnabled,
  443. 'is_menu_enabled' => $this->_menuEnabled,
  444. 'menu' => $menu ?? '',
  445. 'console' => $console ?? '',
  446. 'messages' => $messages ?? '',
  447. 'has_recent_table' => empty($_REQUEST['recent_table']),
  448. 'recent_table' => $recentTable ?? '',
  449. ]);
  450. }
  451. return '';
  452. }
  453. /**
  454. * Returns the message to be displayed at the top of
  455. * the page, including the executed SQL query, if any.
  456. *
  457. * @return string
  458. */
  459. public function getMessage(): string
  460. {
  461. $retval = '';
  462. $message = '';
  463. if (! empty($GLOBALS['message'])) {
  464. $message = $GLOBALS['message'];
  465. unset($GLOBALS['message']);
  466. } elseif (! empty($_REQUEST['message'])) {
  467. $message = $_REQUEST['message'];
  468. }
  469. if (! empty($message)) {
  470. if (isset($GLOBALS['buffer_message'])) {
  471. $buffer_message = $GLOBALS['buffer_message'];
  472. }
  473. $retval .= Util::getMessage($message);
  474. if (isset($buffer_message)) {
  475. $GLOBALS['buffer_message'] = $buffer_message;
  476. }
  477. }
  478. return $retval;
  479. }
  480. /**
  481. * Sends out the HTTP headers
  482. *
  483. * @return void
  484. */
  485. public function sendHttpHeaders(): void
  486. {
  487. if (defined('TESTSUITE')) {
  488. return;
  489. }
  490. $map_tile_urls = ' *.tile.openstreetmap.org';
  491. /**
  492. * Sends http headers
  493. */
  494. $GLOBALS['now'] = gmdate('D, d M Y H:i:s') . ' GMT';
  495. if (! empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
  496. && ! empty($GLOBALS['cfg']['CaptchaLoginPublicKey'])
  497. ) {
  498. $captcha_url
  499. = ' https://apis.google.com https://www.google.com/recaptcha/'
  500. . ' https://www.gstatic.com/recaptcha/ https://ssl.gstatic.com/ ';
  501. } else {
  502. $captcha_url = '';
  503. }
  504. /* Prevent against ClickJacking by disabling framing */
  505. if (strtolower((string) $GLOBALS['cfg']['AllowThirdPartyFraming']) === 'sameorigin') {
  506. header(
  507. 'X-Frame-Options: SAMEORIGIN'
  508. );
  509. } elseif ($GLOBALS['cfg']['AllowThirdPartyFraming'] !== true) {
  510. header(
  511. 'X-Frame-Options: DENY'
  512. );
  513. }
  514. header(
  515. 'Referrer-Policy: no-referrer'
  516. );
  517. header(
  518. "Content-Security-Policy: default-src 'self' "
  519. . $captcha_url
  520. . $GLOBALS['cfg']['CSPAllow'] . ';'
  521. . "script-src 'self' 'unsafe-inline' 'unsafe-eval' "
  522. . $captcha_url
  523. . $GLOBALS['cfg']['CSPAllow'] . ';'
  524. . "style-src 'self' 'unsafe-inline' "
  525. . $captcha_url
  526. . $GLOBALS['cfg']['CSPAllow']
  527. . ";"
  528. . "img-src 'self' data: "
  529. . $GLOBALS['cfg']['CSPAllow']
  530. . $map_tile_urls
  531. . $captcha_url
  532. . ";"
  533. . "object-src 'none';"
  534. );
  535. header(
  536. "X-Content-Security-Policy: default-src 'self' "
  537. . $captcha_url
  538. . $GLOBALS['cfg']['CSPAllow'] . ';'
  539. . "options inline-script eval-script;"
  540. . "referrer no-referrer;"
  541. . "img-src 'self' data: "
  542. . $GLOBALS['cfg']['CSPAllow']
  543. . $map_tile_urls
  544. . $captcha_url
  545. . ";"
  546. . "object-src 'none';"
  547. );
  548. header(
  549. "X-WebKit-CSP: default-src 'self' "
  550. . $captcha_url
  551. . $GLOBALS['cfg']['CSPAllow'] . ';'
  552. . "script-src 'self' "
  553. . $captcha_url
  554. . $GLOBALS['cfg']['CSPAllow']
  555. . " 'unsafe-inline' 'unsafe-eval';"
  556. . "referrer no-referrer;"
  557. . "style-src 'self' 'unsafe-inline' "
  558. . $captcha_url
  559. . ';'
  560. . "img-src 'self' data: "
  561. . $GLOBALS['cfg']['CSPAllow']
  562. . $map_tile_urls
  563. . $captcha_url
  564. . ";"
  565. . "object-src 'none';"
  566. );
  567. // Re-enable possible disabled XSS filters
  568. // see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
  569. header(
  570. 'X-XSS-Protection: 1; mode=block'
  571. );
  572. // "nosniff", prevents Internet Explorer and Google Chrome from MIME-sniffing
  573. // a response away from the declared content-type
  574. // see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
  575. header(
  576. 'X-Content-Type-Options: nosniff'
  577. );
  578. // Adobe cross-domain-policies
  579. // see https://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html
  580. header(
  581. 'X-Permitted-Cross-Domain-Policies: none'
  582. );
  583. // Robots meta tag
  584. // see https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
  585. header(
  586. 'X-Robots-Tag: noindex, nofollow'
  587. );
  588. Core::noCacheHeader();
  589. if (! defined('IS_TRANSFORMATION_WRAPPER')) {
  590. // Define the charset to be used
  591. header('Content-Type: text/html; charset=utf-8');
  592. }
  593. $this->_headerIsSent = true;
  594. }
  595. /**
  596. * If the page is missing the title, this function
  597. * will set it to something reasonable
  598. *
  599. * @return string
  600. */
  601. public function getPageTitle(): string
  602. {
  603. if (strlen($this->_title) == 0) {
  604. if ($GLOBALS['server'] > 0) {
  605. if (strlen($GLOBALS['table'])) {
  606. $temp_title = $GLOBALS['cfg']['TitleTable'];
  607. } elseif (strlen($GLOBALS['db'])) {
  608. $temp_title = $GLOBALS['cfg']['TitleDatabase'];
  609. } elseif (strlen($GLOBALS['cfg']['Server']['host'])) {
  610. $temp_title = $GLOBALS['cfg']['TitleServer'];
  611. } else {
  612. $temp_title = $GLOBALS['cfg']['TitleDefault'];
  613. }
  614. $this->_title = htmlspecialchars(
  615. Util::expandUserString($temp_title)
  616. );
  617. } else {
  618. $this->_title = 'phpMyAdmin';
  619. }
  620. }
  621. return $this->_title;
  622. }
  623. /**
  624. * Add recently used table and reload the navigation.
  625. *
  626. * @param string $db Database name where the table is located.
  627. * @param string $table The table name
  628. *
  629. * @return string
  630. */
  631. private function _addRecentTable(string $db, string $table): string
  632. {
  633. $retval = '';
  634. if ($this->_menuEnabled
  635. && strlen($table) > 0
  636. && $GLOBALS['cfg']['NumRecentTables'] > 0
  637. ) {
  638. $tmp_result = RecentFavoriteTable::getInstance('recent')->add(
  639. $db,
  640. $table
  641. );
  642. if ($tmp_result === true) {
  643. $retval = RecentFavoriteTable::getHtmlUpdateRecentTables();
  644. } else {
  645. $error = $tmp_result;
  646. $retval = $error->getDisplay();
  647. }
  648. }
  649. return $retval;
  650. }
  651. /**
  652. * Returns the phpMyAdmin version to be appended to the url to avoid caching
  653. * between versions
  654. *
  655. * @return string urlenocded pma version as a parameter
  656. */
  657. public static function getVersionParameter(): string
  658. {
  659. return "v=" . urlencode(PMA_VERSION);
  660. }
  661. }