Translated using Weblate (Portuguese)
[phpmyadmin.git] / src / Header.php
blob409571c2ca4a96003833bda39fea06f8a82b05d5
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\Container\ContainerBuilder;
12 use PhpMyAdmin\Html\Generator;
13 use PhpMyAdmin\Navigation\Navigation;
14 use PhpMyAdmin\Theme\ThemeManager;
16 use function array_merge;
17 use function defined;
18 use function gmdate;
19 use function header;
20 use function htmlspecialchars;
21 use function ini_get;
22 use function json_encode;
23 use function sprintf;
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 * Menu instance
41 private Menu $menu;
42 /**
43 * The page title
45 private string $title = '';
46 /**
47 * The value for the id attribute for the body tag
49 private string $bodyId = '';
50 /**
51 * Whether to show the top menu
53 private bool $menuEnabled;
54 /**
55 * Whether to show the warnings
57 private bool $warningsEnabled = true;
58 /**
59 * Whether we are servicing an ajax request.
61 private bool $isAjax = false;
62 /**
63 * Whether to display anything
65 private bool $isEnabled = true;
66 /**
67 * Whether the HTTP headers (and possibly some HTML)
68 * have already been sent to the browser
70 private bool $headerIsSent = false;
72 private UserPreferences $userPreferences;
74 private bool $isTransformationWrapper = false;
76 public function __construct(
77 private readonly Template $template,
78 private readonly Console $console,
79 private readonly Config $config,
80 ) {
81 $dbi = DatabaseInterface::getInstance();
82 $this->menuEnabled = $dbi->isConnected();
83 $relation = new Relation($dbi);
84 $this->menu = new Menu($dbi, $this->template, $this->config, $relation, Current::$database, Current::$table);
85 $this->scripts = new Scripts($this->template);
86 $this->addDefaultScripts();
88 $this->userPreferences = new UserPreferences($dbi, $relation, $this->template);
91 /**
92 * Loads common scripts
94 private function addDefaultScripts(): void
96 $this->scripts->addFile('runtime.js');
97 $this->scripts->addFile('vendor/jquery/jquery.min.js');
98 $this->scripts->addFile('vendor/jquery/jquery-migrate.min.js');
99 $this->scripts->addFile('vendor/sprintf.js');
100 $this->scripts->addFile('vendor/jquery/jquery-ui.min.js');
101 $this->scripts->addFile('name-conflict-fixes.js');
102 $this->scripts->addFile('vendor/bootstrap/bootstrap.bundle.min.js');
103 $this->scripts->addFile('vendor/js.cookie.min.js');
104 $this->scripts->addFile('vendor/jquery/jquery.validate.min.js');
105 $this->scripts->addFile('vendor/jquery/jquery-ui-timepicker-addon.js');
106 $this->scripts->addFile('index.php', ['route' => '/messages', 'l' => $GLOBALS['lang']]);
107 $this->scripts->addFile('shared.js');
108 $this->scripts->addFile('menu_resizer.js');
109 $this->scripts->addFile('main.js');
111 $this->scripts->addCode($this->getJsParamsCode());
115 * Returns, as an array, a list of parameters
116 * used on the client side
118 * @return mixed[]
120 public function getJsParams(): array
122 $pftext = $_SESSION['tmpval']['pftext'] ?? '';
124 $params = [
125 // Do not add any separator, JS code will decide
126 'common_query' => Url::getCommonRaw([], ''),
127 'opendb_url' => Util::getScriptNameForOption($this->config->settings['DefaultTabDatabase'], 'database'),
128 'lang' => $GLOBALS['lang'],
129 'server' => Current::$server,
130 'table' => Current::$table,
131 'db' => Current::$database,
132 'token' => $_SESSION[' PMA_token '],
133 'text_dir' => LanguageManager::$textDir,
134 'LimitChars' => $this->config->settings['LimitChars'],
135 'pftext' => $pftext,
136 'confirm' => $this->config->settings['Confirm'],
137 'LoginCookieValidity' => $this->config->settings['LoginCookieValidity'],
138 'session_gc_maxlifetime' => (int) ini_get('session.gc_maxlifetime'),
139 'logged_in' => DatabaseInterface::getInstance()->isConnected(),
140 'is_https' => $this->config->isHttps(),
141 'rootPath' => $this->config->getRootPath(),
142 'arg_separator' => Url::getArgSeparator(),
143 'version' => Version::VERSION,
145 if ($this->config->hasSelectedServer()) {
146 $params['auth_type'] = $this->config->selectedServer['auth_type'];
147 if (isset($this->config->selectedServer['user'])) {
148 $params['user'] = $this->config->selectedServer['user'];
152 return $params;
156 * Returns, as a string, a list of parameters
157 * used on the client side
159 public function getJsParamsCode(): string
161 $params = $this->getJsParams();
163 return 'window.Navigation.update(window.CommonParams.setAll(' . json_encode($params, JSON_HEX_TAG) . '));';
167 * Disables the rendering of the header
169 public function disable(): void
171 $this->isEnabled = false;
175 * Set the ajax flag to indicate whether
176 * we are servicing an ajax request
178 * @param bool $isAjax Whether we are servicing an ajax request
180 public function setAjax(bool $isAjax): void
182 $this->isAjax = $isAjax;
183 $this->console->setAjax($isAjax);
187 * Returns the Scripts object
189 * @return Scripts object
191 public function getScripts(): Scripts
193 return $this->scripts;
197 * Returns the Menu object
199 * @return Menu object
201 public function getMenu(): Menu
203 return $this->menu;
207 * Setter for the ID attribute in the BODY tag
209 * @param string $id Value for the ID attribute
211 public function setBodyId(string $id): void
213 $this->bodyId = htmlspecialchars($id);
217 * Setter for the title of the page
219 * @param string $title New title
221 public function setTitle(string $title): void
223 $this->title = htmlspecialchars($title);
227 * Disables the display of the top menu
229 public function disableMenuAndConsole(): void
231 $this->menuEnabled = false;
232 $this->console->disable();
236 * Disables the display of the top menu
238 public function disableWarnings(): void
240 $this->warningsEnabled = false;
244 * Generates the header
246 * @return string The header
248 public function getDisplay(): string
250 if ($this->headerIsSent || ! $this->isEnabled || $this->isAjax) {
251 return '';
254 $this->sendHttpHeaders();
256 $baseDir = defined('PMA_PATH_TO_BASEDIR') ? PMA_PATH_TO_BASEDIR : '';
258 /** @var ThemeManager $themeManager */
259 $themeManager = ContainerBuilder::getContainer()->get(ThemeManager::class);
260 $theme = $themeManager->theme;
262 $version = self::getVersionParameter();
264 // The user preferences have been merged at this point
265 // so we can conditionally add CodeMirror, other scripts and settings
266 if ($this->config->settings['CodemirrorEnable']) {
267 $this->scripts->addFile('vendor/codemirror/lib/codemirror.js');
268 $this->scripts->addFile('vendor/codemirror/mode/sql/sql.js');
269 $this->scripts->addFile('vendor/codemirror/addon/runmode/runmode.js');
270 $this->scripts->addFile('vendor/codemirror/addon/hint/show-hint.js');
271 $this->scripts->addFile('vendor/codemirror/addon/hint/sql-hint.js');
272 if ($this->config->settings['LintEnable']) {
273 $this->scripts->addFile('vendor/codemirror/addon/lint/lint.js');
274 $this->scripts->addFile('codemirror/addon/lint/sql-lint.js');
278 if ($this->config->settings['SendErrorReports'] !== 'never') {
279 $this->scripts->addFile('vendor/tracekit.js');
280 $this->scripts->addFile('error_report.js');
283 if ($this->config->settings['enable_drag_drop_import'] === true) {
284 $this->scripts->addFile('drag_drop_import.js');
287 if (! $this->config->get('DisableShortcutKeys')) {
288 $this->scripts->addFile('shortcuts_handler.js');
291 $this->scripts->addCode($this->getVariablesForJavaScript());
293 $this->scripts->addCode(
294 'ConsoleEnterExecutes=' . ($this->config->settings['ConsoleEnterExecutes'] ? 'true' : 'false'),
296 $this->scripts->addFiles($this->console->getScripts());
298 $dbi = DatabaseInterface::getInstance();
299 if ($this->menuEnabled && Current::$server > 0) {
300 $navigation = (new Navigation($this->template, new Relation($dbi), $dbi, $this->config))->getDisplay();
303 $customHeader = Config::renderHeader();
305 // offer to load user preferences from localStorage
306 if (
307 $this->config->get('user_preferences') === 'session'
308 && ! isset($_SESSION['userprefs_autoload'])
310 $loadUserPreferences = $this->userPreferences->autoloadGetHeader();
313 if ($this->menuEnabled && Current::$server > 0) {
314 $menu = $this->menu->getDisplay();
317 $console = $this->console->getDisplay();
318 $messages = $this->getMessage();
319 $isLoggedIn = $dbi->isConnected();
321 $this->scripts->addFile('datetimepicker.js');
322 $this->scripts->addFile('validator-messages.js');
324 return $this->template->render('header', [
325 'lang' => $GLOBALS['lang'],
326 'allow_third_party_framing' => $this->config->settings['AllowThirdPartyFraming'],
327 'base_dir' => $baseDir,
328 'theme_path' => $theme->getPath(),
329 'version' => $version,
330 'text_dir' => LanguageManager::$textDir,
331 'server' => Current::$server,
332 'title' => $this->getPageTitle(),
333 'scripts' => $this->scripts->getDisplay(),
334 'body_id' => $this->bodyId,
335 'navigation' => $navigation ?? '',
336 'custom_header' => $customHeader,
337 'load_user_preferences' => $loadUserPreferences ?? '',
338 'show_hint' => $this->config->settings['ShowHint'],
339 'is_warnings_enabled' => $this->warningsEnabled,
340 'is_menu_enabled' => $this->menuEnabled,
341 'is_logged_in' => $isLoggedIn,
342 'menu' => $menu ?? '',
343 'console' => $console,
344 'messages' => $messages,
345 'theme_color_mode' => $theme->getColorMode(),
346 'theme_color_modes' => $theme->getColorModes(),
347 'theme_id' => $theme->getId(),
348 'current_user' => $dbi->getCurrentUserAndHost(),
349 'is_mariadb' => $dbi->isMariaDB(),
354 * Returns the message to be displayed at the top of
355 * the page, including the executed SQL query, if any.
357 public function getMessage(): string
359 $retval = '';
360 $message = '';
361 if (! empty($GLOBALS['message'])) {
362 $message = $GLOBALS['message'];
363 unset($GLOBALS['message']);
364 } elseif (! empty($_REQUEST['message'])) {
365 $message = $_REQUEST['message'];
368 if ($message !== '') {
369 if (isset($GLOBALS['buffer_message'])) {
370 $bufferMessage = $GLOBALS['buffer_message'];
373 $retval .= Generator::getMessage($message);
374 if (isset($bufferMessage)) {
375 $GLOBALS['buffer_message'] = $bufferMessage;
379 return $retval;
383 * Sends out the HTTP headers
385 public function sendHttpHeaders(): void
387 if (defined('TESTSUITE')) {
388 return;
392 * Sends http headers
394 $GLOBALS['now'] = gmdate('D, d M Y H:i:s') . ' GMT';
396 $headers = $this->getHttpHeaders();
398 foreach ($headers as $name => $value) {
399 header(sprintf('%s: %s', $name, $value));
402 $this->headerIsSent = true;
405 /** @return array<string, string> */
406 public function getHttpHeaders(): array
408 $headers = [];
410 /* Prevent against ClickJacking by disabling framing */
411 if (strtolower((string) $this->config->settings['AllowThirdPartyFraming']) === 'sameorigin') {
412 $headers['X-Frame-Options'] = 'SAMEORIGIN';
413 } elseif ($this->config->settings['AllowThirdPartyFraming'] !== true) {
414 $headers['X-Frame-Options'] = 'DENY';
417 $headers['Referrer-Policy'] = 'same-origin';
419 $headers = array_merge($headers, $this->getCspHeaders());
422 * Re-enable possible disabled XSS filters.
424 * @see https://developer.mozilla.org/docs/Web/HTTP/Headers/X-XSS-Protection
426 $headers['X-XSS-Protection'] = '1; mode=block';
429 * "nosniff", prevents Internet Explorer and Google Chrome from MIME-sniffing
430 * a response away from the declared content-type.
432 * @see https://developer.mozilla.org/docs/Web/HTTP/Headers/X-Content-Type-Options
434 $headers['X-Content-Type-Options'] = 'nosniff';
437 * Adobe cross-domain-policies.
439 * @see https://www.sentrium.co.uk/labs/application-security-101-http-headers
441 $headers['X-Permitted-Cross-Domain-Policies'] = 'none';
444 * Robots meta tag.
446 * @see https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag
448 $headers['X-Robots-Tag'] = 'noindex, nofollow';
451 * The HTTP Permissions-Policy header provides a mechanism to allow and deny
452 * the use of browser features in a document
453 * or within any <iframe> elements in the document.
455 * @see https://developer.mozilla.org/docs/Web/HTTP/Headers/Permissions-Policy
457 $headers['Permissions-Policy'] = 'fullscreen=(self), oversized-images=(self), interest-cohort=()';
459 $headers = array_merge($headers, Core::getNoCacheHeaders());
462 * A different Content-Type is set in {@see \PhpMyAdmin\Controllers\Transformation\WrapperController}.
464 if (! $this->isTransformationWrapper) {
465 // Define the charset to be used
466 $headers['Content-Type'] = 'text/html; charset=utf-8';
469 return $headers;
473 * If the page is missing the title, this function
474 * will set it to something reasonable
476 public function getPageTitle(): string
478 if ($this->title === '') {
479 if (Current::$server > 0) {
480 if (Current::$table !== '') {
481 $tempTitle = $this->config->settings['TitleTable'];
482 } elseif (Current::$database !== '') {
483 $tempTitle = $this->config->settings['TitleDatabase'];
484 } elseif ($this->config->selectedServer['host'] !== '') {
485 $tempTitle = $this->config->settings['TitleServer'];
486 } else {
487 $tempTitle = $this->config->settings['TitleDefault'];
490 $this->title = htmlspecialchars(
491 Util::expandUserString($tempTitle),
493 } else {
494 $this->title = 'phpMyAdmin';
498 return $this->title;
502 * Get all the CSP allow policy headers
504 * @return array<string, string>
506 private function getCspHeaders(): array
508 $mapTileUrl = ' tile.openstreetmap.org';
509 $captchaUrl = '';
510 $cspAllow = $this->config->settings['CSPAllow'];
512 if (
513 ! empty($this->config->settings['CaptchaLoginPrivateKey'])
514 && ! empty($this->config->settings['CaptchaLoginPublicKey'])
515 && ! empty($this->config->settings['CaptchaApi'])
516 && ! empty($this->config->settings['CaptchaRequestParam'])
517 && ! empty($this->config->settings['CaptchaResponseParam'])
519 $captchaUrl = ' ' . $this->config->settings['CaptchaCsp'] . ' ';
522 $headers = [];
524 $headers['Content-Security-Policy'] = sprintf(
525 'default-src \'self\' %s%s;script-src \'self\' \'unsafe-inline\' \'unsafe-eval\' %s%s;'
526 . 'style-src \'self\' \'unsafe-inline\' %s%s;img-src \'self\' data: %s%s%s;object-src \'none\';',
527 $captchaUrl,
528 $cspAllow,
529 $captchaUrl,
530 $cspAllow,
531 $captchaUrl,
532 $cspAllow,
533 $cspAllow,
534 $mapTileUrl,
535 $captchaUrl,
538 $headers['X-Content-Security-Policy'] = sprintf(
539 'default-src \'self\' %s%s;options inline-script eval-script;'
540 . 'referrer no-referrer;img-src \'self\' data: %s%s%s;object-src \'none\';',
541 $captchaUrl,
542 $cspAllow,
543 $cspAllow,
544 $mapTileUrl,
545 $captchaUrl,
548 $headers['X-WebKit-CSP'] = sprintf(
549 'default-src \'self\' %s%s;script-src \'self\' %s%s \'unsafe-inline\' \'unsafe-eval\';'
550 . 'referrer no-referrer;style-src \'self\' \'unsafe-inline\' %s;'
551 . 'img-src \'self\' data: %s%s%s;object-src \'none\';',
552 $captchaUrl,
553 $cspAllow,
554 $captchaUrl,
555 $cspAllow,
556 $captchaUrl,
557 $cspAllow,
558 $mapTileUrl,
559 $captchaUrl,
562 return $headers;
566 * Returns the phpMyAdmin version to be appended to the url to avoid caching
567 * between versions
569 * @return string urlencoded pma version as a parameter
571 public static function getVersionParameter(): string
573 return 'v=' . urlencode(Version::VERSION);
576 private function getVariablesForJavaScript(): string
578 $maxInputVars = ini_get('max_input_vars');
579 $maxInputVarsValue = $maxInputVars === false || $maxInputVars === '' ? 'false' : (int) $maxInputVars;
581 return $this->template->render('javascript/variables', [
582 'first_day_of_calendar' => $this->config->settings['FirstDayOfCalendar'] ?? 0,
583 'max_input_vars' => $maxInputVarsValue,
587 public function setIsTransformationWrapper(bool $isTransformationWrapper): void
589 $this->isTransformationWrapper = $isTransformationWrapper;