Translated using Weblate (Russian)
[phpmyadmin.git] / src / Header.php
bloba73c42578a80eef4035e019f6b4a1e89e44e528e
1 <?php
2 /**
3 * Used to render the header of PMA's pages
4 */
6 declare(strict_types=1);
8 namespace PhpMyAdmin;
10 use PhpMyAdmin\ConfigStorage\Relation;
11 use PhpMyAdmin\Html\Generator;
12 use PhpMyAdmin\Navigation\Navigation;
13 use PhpMyAdmin\Theme\ThemeManager;
15 use function array_merge;
16 use function defined;
17 use function gmdate;
18 use function header;
19 use function htmlspecialchars;
20 use function ini_get;
21 use function json_encode;
22 use function sprintf;
23 use function strlen;
24 use function strtolower;
25 use function urlencode;
27 use const JSON_HEX_TAG;
29 /**
30 * Class used to output the HTTP and HTML headers
32 class Header
34 /**
35 * Scripts instance
37 private Scripts $scripts;
38 /**
39 * PhpMyAdmin\Console instance
41 private Console $console;
42 /**
43 * Menu instance
45 private Menu $menu;
46 /**
47 * The page title
49 private string $title = '';
50 /**
51 * The value for the id attribute for the body tag
53 private string $bodyId = '';
54 /**
55 * Whether to show the top menu
57 private bool $menuEnabled;
58 /**
59 * Whether to show the warnings
61 private bool $warningsEnabled = true;
62 /**
63 * Whether we are servicing an ajax request.
65 private bool $isAjax = false;
66 /**
67 * Whether to display anything
69 private bool $isEnabled = true;
70 /**
71 * Whether the HTTP headers (and possibly some HTML)
72 * have already been sent to the browser
74 private bool $headerIsSent = false;
76 private UserPreferences $userPreferences;
78 private bool $isTransformationWrapper = false;
80 /**
81 * Creates a new class instance
83 public function __construct(private readonly Template $template, Console $console)
85 $dbi = DatabaseInterface::getInstance();
86 $this->console = $console;
87 $this->menuEnabled = $dbi->isConnected();
88 $this->menu = new Menu($dbi, $this->template, $GLOBALS['db'] ?? '', $GLOBALS['table'] ?? '');
89 $this->scripts = new Scripts($this->template);
90 $this->addDefaultScripts();
92 $this->userPreferences = new UserPreferences($dbi, new Relation($dbi), $this->template);
95 /**
96 * Loads common scripts
98 private function addDefaultScripts(): void
100 $this->scripts->addFile('runtime.js');
101 $this->scripts->addFile('vendor/jquery/jquery.min.js');
102 $this->scripts->addFile('vendor/jquery/jquery-migrate.min.js');
103 $this->scripts->addFile('vendor/sprintf.js');
104 $this->scripts->addFile('vendor/jquery/jquery-ui.min.js');
105 $this->scripts->addFile('name-conflict-fixes.js');
106 $this->scripts->addFile('vendor/bootstrap/bootstrap.bundle.min.js');
107 $this->scripts->addFile('vendor/js.cookie.min.js');
108 $this->scripts->addFile('vendor/jquery/jquery.validate.min.js');
109 $this->scripts->addFile('vendor/jquery/jquery-ui-timepicker-addon.js');
110 $this->scripts->addFile('index.php', ['route' => '/messages', 'l' => $GLOBALS['lang']]);
111 $this->scripts->addFile('shared.js');
112 $this->scripts->addFile('menu_resizer.js');
113 $this->scripts->addFile('main.js');
115 $this->scripts->addCode($this->getJsParamsCode());
119 * Returns, as an array, a list of parameters
120 * used on the client side
122 * @return mixed[]
124 public function getJsParams(): array
126 $pftext = $_SESSION['tmpval']['pftext'] ?? '';
128 $config = Config::getInstance();
129 $params = [
130 // Do not add any separator, JS code will decide
131 'common_query' => Url::getCommonRaw([], ''),
132 'opendb_url' => Util::getScriptNameForOption($config->settings['DefaultTabDatabase'], 'database'),
133 'lang' => $GLOBALS['lang'],
134 'server' => $GLOBALS['server'],
135 'table' => $GLOBALS['table'] ?? '',
136 'db' => $GLOBALS['db'] ?? '',
137 'token' => $_SESSION[' PMA_token '],
138 'text_dir' => $GLOBALS['text_dir'],
139 'LimitChars' => $config->settings['LimitChars'],
140 'pftext' => $pftext,
141 'confirm' => $config->settings['Confirm'],
142 'LoginCookieValidity' => $config->settings['LoginCookieValidity'],
143 'session_gc_maxlifetime' => (int) ini_get('session.gc_maxlifetime'),
144 'logged_in' => DatabaseInterface::getInstance()->isConnected(),
145 'is_https' => $config->isHttps(),
146 'rootPath' => $config->getRootPath(),
147 'arg_separator' => Url::getArgSeparator(),
148 'version' => Version::VERSION,
150 if ($config->hasSelectedServer()) {
151 $params['auth_type'] = $config->selectedServer['auth_type'];
152 if (isset($config->selectedServer['user'])) {
153 $params['user'] = $config->selectedServer['user'];
157 return $params;
161 * Returns, as a string, a list of parameters
162 * used on the client side
164 public function getJsParamsCode(): string
166 $params = $this->getJsParams();
168 return 'window.Navigation.update(window.CommonParams.setAll(' . json_encode($params, JSON_HEX_TAG) . '));';
172 * Disables the rendering of the header
174 public function disable(): void
176 $this->isEnabled = false;
180 * Set the ajax flag to indicate whether
181 * we are servicing an ajax request
183 * @param bool $isAjax Whether we are servicing an ajax request
185 public function setAjax(bool $isAjax): void
187 $this->isAjax = $isAjax;
188 $this->console->setAjax($isAjax);
192 * Returns the Scripts object
194 * @return Scripts object
196 public function getScripts(): Scripts
198 return $this->scripts;
202 * Returns the Menu object
204 * @return Menu object
206 public function getMenu(): Menu
208 return $this->menu;
212 * Setter for the ID attribute in the BODY tag
214 * @param string $id Value for the ID attribute
216 public function setBodyId(string $id): void
218 $this->bodyId = htmlspecialchars($id);
222 * Setter for the title of the page
224 * @param string $title New title
226 public function setTitle(string $title): void
228 $this->title = htmlspecialchars($title);
232 * Disables the display of the top menu
234 public function disableMenuAndConsole(): void
236 $this->menuEnabled = false;
237 $this->console->disable();
241 * Disables the display of the top menu
243 public function disableWarnings(): void
245 $this->warningsEnabled = false;
249 * Generates the header
251 * @return string The header
253 public function getDisplay(): string
255 if ($this->headerIsSent || ! $this->isEnabled) {
256 return '';
259 $recentTable = '';
260 if (empty($_REQUEST['recent_table'])) {
261 $recentTable = $this->addRecentTable($GLOBALS['db'], $GLOBALS['table']);
264 if ($this->isAjax) {
265 return $recentTable;
268 $this->sendHttpHeaders();
270 $baseDir = defined('PMA_PATH_TO_BASEDIR') ? PMA_PATH_TO_BASEDIR : '';
272 /** @var ThemeManager $themeManager */
273 $themeManager = Core::getContainerBuilder()->get(ThemeManager::class);
274 $theme = $themeManager->theme;
276 $version = self::getVersionParameter();
278 // The user preferences have been merged at this point
279 // so we can conditionally add CodeMirror, other scripts and settings
280 $config = Config::getInstance();
281 if ($config->settings['CodemirrorEnable']) {
282 $this->scripts->addFile('vendor/codemirror/lib/codemirror.js');
283 $this->scripts->addFile('vendor/codemirror/mode/sql/sql.js');
284 $this->scripts->addFile('vendor/codemirror/addon/runmode/runmode.js');
285 $this->scripts->addFile('vendor/codemirror/addon/hint/show-hint.js');
286 $this->scripts->addFile('vendor/codemirror/addon/hint/sql-hint.js');
287 if ($config->settings['LintEnable']) {
288 $this->scripts->addFile('vendor/codemirror/addon/lint/lint.js');
289 $this->scripts->addFile('codemirror/addon/lint/sql-lint.js');
293 if ($config->settings['SendErrorReports'] !== 'never') {
294 $this->scripts->addFile('vendor/tracekit.js');
295 $this->scripts->addFile('error_report.js');
298 if ($config->settings['enable_drag_drop_import'] === true) {
299 $this->scripts->addFile('drag_drop_import.js');
302 if (! $config->get('DisableShortcutKeys')) {
303 $this->scripts->addFile('shortcuts_handler.js');
306 $this->scripts->addCode($this->getVariablesForJavaScript());
308 $this->scripts->addCode(
309 'ConsoleEnterExecutes=' . ($config->settings['ConsoleEnterExecutes'] ? 'true' : 'false'),
311 $this->scripts->addFiles($this->console->getScripts());
313 $dbi = DatabaseInterface::getInstance();
314 if ($this->menuEnabled && $GLOBALS['server'] > 0) {
315 $nav = new Navigation(
316 $this->template,
317 new Relation($dbi),
318 $dbi,
320 $navigation = $nav->getDisplay();
323 $customHeader = Config::renderHeader();
325 // offer to load user preferences from localStorage
326 if (
327 $config->get('user_preferences') === 'session'
328 && ! isset($_SESSION['userprefs_autoload'])
330 $loadUserPreferences = $this->userPreferences->autoloadGetHeader();
333 if ($this->menuEnabled && $GLOBALS['server'] > 0) {
334 $menu = $this->menu->getDisplay();
337 $console = $this->console->getDisplay();
338 $messages = $this->getMessage();
339 $isLoggedIn = $dbi->isConnected();
341 $this->scripts->addFile('datetimepicker.js');
342 $this->scripts->addFile('validator-messages.js');
344 return $this->template->render('header', [
345 'lang' => $GLOBALS['lang'],
346 'allow_third_party_framing' => $config->settings['AllowThirdPartyFraming'],
347 'base_dir' => $baseDir,
348 'theme_path' => $theme->getPath(),
349 'version' => $version,
350 'text_dir' => $GLOBALS['text_dir'],
351 'server' => $GLOBALS['server'] ?? null,
352 'title' => $this->getPageTitle(),
353 'scripts' => $this->scripts->getDisplay(),
354 'body_id' => $this->bodyId,
355 'navigation' => $navigation ?? '',
356 'custom_header' => $customHeader,
357 'load_user_preferences' => $loadUserPreferences ?? '',
358 'show_hint' => $config->settings['ShowHint'],
359 'is_warnings_enabled' => $this->warningsEnabled,
360 'is_menu_enabled' => $this->menuEnabled,
361 'is_logged_in' => $isLoggedIn,
362 'menu' => $menu ?? '',
363 'console' => $console,
364 'messages' => $messages,
365 'recent_table' => $recentTable,
366 'theme_color_mode' => $theme->getColorMode(),
367 'theme_color_modes' => $theme->getColorModes(),
368 'theme_id' => $theme->getId(),
369 'current_user' => $dbi->getCurrentUserAndHost(),
370 'is_mariadb' => $dbi->isMariaDB(),
375 * Returns the message to be displayed at the top of
376 * the page, including the executed SQL query, if any.
378 public function getMessage(): string
380 $retval = '';
381 $message = '';
382 if (! empty($GLOBALS['message'])) {
383 $message = $GLOBALS['message'];
384 unset($GLOBALS['message']);
385 } elseif (! empty($_REQUEST['message'])) {
386 $message = $_REQUEST['message'];
389 if (! empty($message)) {
390 if (isset($GLOBALS['buffer_message'])) {
391 $bufferMessage = $GLOBALS['buffer_message'];
394 $retval .= Generator::getMessage($message);
395 if (isset($bufferMessage)) {
396 $GLOBALS['buffer_message'] = $bufferMessage;
400 return $retval;
404 * Sends out the HTTP headers
406 public function sendHttpHeaders(): void
408 if (defined('TESTSUITE')) {
409 return;
413 * Sends http headers
415 $GLOBALS['now'] = gmdate('D, d M Y H:i:s') . ' GMT';
417 $headers = $this->getHttpHeaders();
419 foreach ($headers as $name => $value) {
420 header(sprintf('%s: %s', $name, $value));
423 $this->headerIsSent = true;
426 /** @return array<string, string> */
427 public function getHttpHeaders(): array
429 $headers = [];
431 /* Prevent against ClickJacking by disabling framing */
432 $config = Config::getInstance();
433 if (strtolower((string) $config->settings['AllowThirdPartyFraming']) === 'sameorigin') {
434 $headers['X-Frame-Options'] = 'SAMEORIGIN';
435 } elseif ($config->settings['AllowThirdPartyFraming'] !== true) {
436 $headers['X-Frame-Options'] = 'DENY';
439 $headers['Referrer-Policy'] = 'no-referrer';
441 $headers = array_merge($headers, $this->getCspHeaders());
444 * Re-enable possible disabled XSS filters.
446 * @see https://developer.mozilla.org/docs/Web/HTTP/Headers/X-XSS-Protection
448 $headers['X-XSS-Protection'] = '1; mode=block';
451 * "nosniff", prevents Internet Explorer and Google Chrome from MIME-sniffing
452 * a response away from the declared content-type.
454 * @see https://developer.mozilla.org/docs/Web/HTTP/Headers/X-Content-Type-Options
456 $headers['X-Content-Type-Options'] = 'nosniff';
459 * Adobe cross-domain-policies.
461 * @see https://www.sentrium.co.uk/labs/application-security-101-http-headers
463 $headers['X-Permitted-Cross-Domain-Policies'] = 'none';
466 * Robots meta tag.
468 * @see https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag
470 $headers['X-Robots-Tag'] = 'noindex, nofollow';
473 * The HTTP Permissions-Policy header provides a mechanism to allow and deny
474 * the use of browser features in a document
475 * or within any <iframe> elements in the document.
477 * @see https://developer.mozilla.org/docs/Web/HTTP/Headers/Permissions-Policy
479 $headers['Permissions-Policy'] = 'fullscreen=(self), oversized-images=(self), interest-cohort=()';
481 $headers = array_merge($headers, Core::getNoCacheHeaders());
484 * A different Content-Type is set in {@see \PhpMyAdmin\Controllers\Transformation\WrapperController}.
486 if (! $this->isTransformationWrapper) {
487 // Define the charset to be used
488 $headers['Content-Type'] = 'text/html; charset=utf-8';
491 return $headers;
495 * If the page is missing the title, this function
496 * will set it to something reasonable
498 public function getPageTitle(): string
500 if (strlen($this->title) == 0) {
501 if ($GLOBALS['server'] > 0) {
502 $config = Config::getInstance();
503 if ($GLOBALS['table'] !== '') {
504 $tempTitle = $config->settings['TitleTable'];
505 } elseif ($GLOBALS['db'] !== '') {
506 $tempTitle = $config->settings['TitleDatabase'];
507 } elseif ($config->selectedServer['host'] !== '') {
508 $tempTitle = $config->settings['TitleServer'];
509 } else {
510 $tempTitle = $config->settings['TitleDefault'];
513 $this->title = htmlspecialchars(
514 Util::expandUserString($tempTitle),
516 } else {
517 $this->title = 'phpMyAdmin';
521 return $this->title;
525 * Get all the CSP allow policy headers
527 * @return array<string, string>
529 private function getCspHeaders(): array
531 $mapTileUrl = ' tile.openstreetmap.org';
532 $captchaUrl = '';
533 $config = Config::getInstance();
534 $cspAllow = $config->settings['CSPAllow'];
536 if (
537 ! empty($config->settings['CaptchaLoginPrivateKey'])
538 && ! empty($config->settings['CaptchaLoginPublicKey'])
539 && ! empty($config->settings['CaptchaApi'])
540 && ! empty($config->settings['CaptchaRequestParam'])
541 && ! empty($config->settings['CaptchaResponseParam'])
543 $captchaUrl = ' ' . $config->settings['CaptchaCsp'] . ' ';
546 $headers = [];
548 $headers['Content-Security-Policy'] = sprintf(
549 'default-src \'self\' %s%s;script-src \'self\' \'unsafe-inline\' \'unsafe-eval\' %s%s;'
550 . 'style-src \'self\' \'unsafe-inline\' %s%s;img-src \'self\' data: %s%s%s;object-src \'none\';',
551 $captchaUrl,
552 $cspAllow,
553 $captchaUrl,
554 $cspAllow,
555 $captchaUrl,
556 $cspAllow,
557 $cspAllow,
558 $mapTileUrl,
559 $captchaUrl,
562 $headers['X-Content-Security-Policy'] = sprintf(
563 'default-src \'self\' %s%s;options inline-script eval-script;'
564 . 'referrer no-referrer;img-src \'self\' data: %s%s%s;object-src \'none\';',
565 $captchaUrl,
566 $cspAllow,
567 $cspAllow,
568 $mapTileUrl,
569 $captchaUrl,
572 $headers['X-WebKit-CSP'] = sprintf(
573 'default-src \'self\' %s%s;script-src \'self\' %s%s \'unsafe-inline\' \'unsafe-eval\';'
574 . 'referrer no-referrer;style-src \'self\' \'unsafe-inline\' %s;'
575 . 'img-src \'self\' data: %s%s%s;object-src \'none\';',
576 $captchaUrl,
577 $cspAllow,
578 $captchaUrl,
579 $cspAllow,
580 $captchaUrl,
581 $cspAllow,
582 $mapTileUrl,
583 $captchaUrl,
586 return $headers;
590 * Add recently used table and reload the navigation.
592 * @param string $db Database name where the table is located.
593 * @param string $table The table name
595 private function addRecentTable(string $db, string $table): string
597 if ($this->menuEnabled && $table !== '' && Config::getInstance()->settings['NumRecentTables'] > 0) {
598 $error = RecentFavoriteTable::getInstance('recent')->add($db, $table);
599 if ($error === true) {
600 return RecentFavoriteTable::getHtmlUpdateRecentTables();
603 return $error->getDisplay();
606 return '';
610 * Returns the phpMyAdmin version to be appended to the url to avoid caching
611 * between versions
613 * @return string urlencoded pma version as a parameter
615 public static function getVersionParameter(): string
617 return 'v=' . urlencode(Version::VERSION);
620 private function getVariablesForJavaScript(): string
622 $maxInputVars = ini_get('max_input_vars');
623 $maxInputVarsValue = $maxInputVars === false || $maxInputVars === '' ? 'false' : (int) $maxInputVars;
625 return $this->template->render('javascript/variables', [
626 'first_day_of_calendar' => Config::getInstance()->settings['FirstDayOfCalendar'] ?? 0,
627 'max_input_vars' => $maxInputVarsValue,
631 public function setIsTransformationWrapper(bool $isTransformationWrapper): void
633 $this->isTransformationWrapper = $isTransformationWrapper;