Translated using Weblate (Portuguese)
[phpmyadmin.git] / src / Config.php
blob88725fda79b9b1b822536fa5e5e1368b714fa984
1 <?php
3 declare(strict_types=1);
5 namespace PhpMyAdmin;
7 use PhpMyAdmin\Config\Settings;
8 use PhpMyAdmin\Config\Settings\Server;
9 use PhpMyAdmin\ConfigStorage\Relation;
10 use PhpMyAdmin\Dbal\ConnectionType;
11 use PhpMyAdmin\Exceptions\ConfigException;
12 use PhpMyAdmin\Routing\Routing;
13 use PhpMyAdmin\Theme\ThemeManager;
14 use Throwable;
16 use function __;
17 use function array_key_last;
18 use function array_replace_recursive;
19 use function array_slice;
20 use function count;
21 use function defined;
22 use function error_reporting;
23 use function explode;
24 use function fclose;
25 use function file_exists;
26 use function filemtime;
27 use function fileperms;
28 use function fopen;
29 use function fread;
30 use function function_exists;
31 use function gd_info;
32 use function implode;
33 use function ini_get;
34 use function is_array;
35 use function is_bool;
36 use function is_dir;
37 use function is_numeric;
38 use function is_readable;
39 use function is_string;
40 use function is_writable;
41 use function mb_strtolower;
42 use function md5;
43 use function min;
44 use function mkdir;
45 use function ob_end_clean;
46 use function ob_get_clean;
47 use function ob_start;
48 use function parse_url;
49 use function preg_match;
50 use function realpath;
51 use function rtrim;
52 use function setcookie;
53 use function sprintf;
54 use function str_contains;
55 use function str_ends_with;
56 use function stripos;
57 use function strtolower;
58 use function sys_get_temp_dir;
59 use function time;
60 use function trim;
62 use const CHANGELOG_FILE;
63 use const DIRECTORY_SEPARATOR;
64 use const PHP_OS;
65 use const PHP_URL_PATH;
66 use const PHP_URL_SCHEME;
68 /**
69 * Configuration handling
71 * @psalm-import-type ServerSettingsType from Server
72 * @psalm-import-type SettingsType from Settings
74 class Config
76 public static self|null $instance = null;
78 /** @var mixed[] default configuration settings */
79 public array $default;
81 /** @var mixed[] configuration settings, without user preferences applied */
82 public array $baseSettings;
84 /** @psalm-var SettingsType */
85 public array $settings;
87 /** @var string config source */
88 public string $source = '';
90 /** @var int source modification time */
91 public int $sourceMtime = 0;
93 public bool $errorConfigFile = false;
95 private bool $isHttps = false;
97 public Settings $config;
98 /** @var int<0, max> */
99 public int $server = 0;
101 /** @var array<string,string|null> $tempDir */
102 private static array $tempDir = [];
104 private bool $hasSelectedServer = false;
106 /** @psalm-var ServerSettingsType */
107 public array $selectedServer;
109 public function __construct()
111 $this->config = new Settings([]);
112 $config = $this->config->asArray();
113 $this->default = $config;
114 $this->settings = $config;
115 $this->baseSettings = $config;
116 $this->selectedServer = (new Server())->asArray();
119 /** @deprecated Use dependency injection instead. */
120 public static function getInstance(): self
122 if (self::$instance === null) {
123 self::$instance = new self();
126 return self::$instance;
130 * @param string|null $source source to read config from
132 * @throws ConfigException
134 public function loadAndCheck(string|null $source = null): void
136 $this->settings['is_setup'] = false;
138 // functions need to refresh in case of config file changed goes in PhpMyAdmin\Config::load()
139 $this->load($source);
141 // other settings, independent of config file, comes in
142 $this->checkSystem();
144 $this->isHttps = $this->isHttps();
145 $this->baseSettings = $this->settings;
149 * sets system and application settings
151 public function checkSystem(): void
153 $this->checkWebServerOs();
154 $this->checkGd2();
155 $this->checkClient();
156 $this->checkUpload();
157 $this->checkUploadSize();
158 $this->checkOutputCompression();
162 * whether to use gzip output compression or not
164 public function checkOutputCompression(): void
166 // If zlib output compression is set in the php configuration file, no
167 // output buffering should be run
168 if (ini_get('zlib.output_compression')) {
169 $this->set('OBGzip', false);
172 // enable output-buffering (if set to 'auto')
173 if (strtolower((string) $this->get('OBGzip')) !== 'auto') {
174 return;
177 $this->set('OBGzip', true);
181 * Sets the client platform based on user agent
183 * @param string $userAgent the user agent
185 private function setClientPlatform(string $userAgent): void
187 if (str_contains($userAgent, 'Win')) {
188 $this->set('PMA_USR_OS', 'Win');
189 } elseif (str_contains($userAgent, 'Mac')) {
190 $this->set('PMA_USR_OS', 'Mac');
191 } elseif (str_contains($userAgent, 'Linux')) {
192 $this->set('PMA_USR_OS', 'Linux');
193 } elseif (str_contains($userAgent, 'Unix')) {
194 $this->set('PMA_USR_OS', 'Unix');
195 } elseif (str_contains($userAgent, 'OS/2')) {
196 $this->set('PMA_USR_OS', 'OS/2');
197 } else {
198 $this->set('PMA_USR_OS', 'Other');
203 * Determines platform (OS), browser and version of the user
204 * Based on a phpBuilder article:
206 * @see http://www.phpbuilder.net/columns/tim20000821.php
208 public function checkClient(): void
210 $httpUserAgent = '';
211 if (Core::getEnv('HTTP_USER_AGENT') !== '') {
212 $httpUserAgent = Core::getEnv('HTTP_USER_AGENT');
215 // 1. Platform
216 $this->setClientPlatform($httpUserAgent);
218 // 2. browser and version
219 // (must check everything else before Mozilla)
221 $isMozilla = preg_match('@Mozilla/([0-9]\.[0-9]{1,2})@', $httpUserAgent, $mozillaVersion);
223 if (preg_match('@Opera(/| )([0-9]\.[0-9]{1,2})@', $httpUserAgent, $logVersion)) {
224 $this->set('PMA_USR_BROWSER_VER', $logVersion[2]);
225 $this->set('PMA_USR_BROWSER_AGENT', 'OPERA');
226 } elseif (preg_match('@(MS)?IE ([0-9]{1,2}\.[0-9]{1,2})@', $httpUserAgent, $logVersion)) {
227 $this->set('PMA_USR_BROWSER_VER', $logVersion[2]);
228 $this->set('PMA_USR_BROWSER_AGENT', 'IE');
229 } elseif (preg_match('@Trident/(7)\.0@', $httpUserAgent, $logVersion)) {
230 $this->set('PMA_USR_BROWSER_VER', (int) $logVersion[1] + 4);
231 $this->set('PMA_USR_BROWSER_AGENT', 'IE');
232 } elseif (preg_match('@OmniWeb/([0-9]{1,3})@', $httpUserAgent, $logVersion)) {
233 $this->set('PMA_USR_BROWSER_VER', $logVersion[1]);
234 $this->set('PMA_USR_BROWSER_AGENT', 'OMNIWEB');
235 // Konqueror 2.2.2 says Konqueror/2.2.2
236 // Konqueror 3.0.3 says Konqueror/3
237 } elseif (preg_match('@(Konqueror/)(.*)(;)@', $httpUserAgent, $logVersion)) {
238 $this->set('PMA_USR_BROWSER_VER', $logVersion[2]);
239 $this->set('PMA_USR_BROWSER_AGENT', 'KONQUEROR');
240 // must check Chrome before Safari
241 } elseif ($isMozilla && preg_match('@Chrome/([0-9.]*)@', $httpUserAgent, $logVersion)) {
242 $this->set('PMA_USR_BROWSER_VER', $logVersion[1]);
243 $this->set('PMA_USR_BROWSER_AGENT', 'CHROME');
244 // newer Safari
245 } elseif ($isMozilla && preg_match('@Version/(.*) Safari@', $httpUserAgent, $logVersion)) {
246 $this->set('PMA_USR_BROWSER_VER', $logVersion[1]);
247 $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI');
248 // older Safari
249 } elseif ($isMozilla && preg_match('@Safari/([0-9]*)@', $httpUserAgent, $logVersion)) {
250 $this->set('PMA_USR_BROWSER_VER', $mozillaVersion[1] . '.' . $logVersion[1]);
251 $this->set('PMA_USR_BROWSER_AGENT', 'SAFARI');
252 // Firefox
253 } elseif (
254 ! str_contains($httpUserAgent, 'compatible')
255 && preg_match('@Firefox/([\w.]+)@', $httpUserAgent, $logVersion)
257 $this->set('PMA_USR_BROWSER_VER', $logVersion[1]);
258 $this->set('PMA_USR_BROWSER_AGENT', 'FIREFOX');
259 } elseif (preg_match('@rv:1\.9(.*)Gecko@', $httpUserAgent)) {
260 $this->set('PMA_USR_BROWSER_VER', '1.9');
261 $this->set('PMA_USR_BROWSER_AGENT', 'GECKO');
262 } elseif ($isMozilla) {
263 $this->set('PMA_USR_BROWSER_VER', $mozillaVersion[1]);
264 $this->set('PMA_USR_BROWSER_AGENT', 'MOZILLA');
265 } else {
266 $this->set('PMA_USR_BROWSER_VER', 0);
267 $this->set('PMA_USR_BROWSER_AGENT', 'OTHER');
272 * Whether GD2 is present
274 public function checkGd2(): void
276 if ($this->get('GD2Available') === 'yes') {
277 $this->set('PMA_IS_GD2', 1);
279 return;
282 if ($this->get('GD2Available') === 'no') {
283 $this->set('PMA_IS_GD2', 0);
285 return;
288 if (! function_exists('imagecreatetruecolor')) {
289 $this->set('PMA_IS_GD2', 0);
291 return;
294 if (function_exists('gd_info')) {
295 $gdInfo = gd_info();
296 if (str_contains($gdInfo['GD Version'], '2.')) {
297 $this->set('PMA_IS_GD2', 1);
299 return;
303 $this->set('PMA_IS_GD2', 0);
307 * Whether the os php is running on is windows or not
309 public function checkWebServerOs(): void
311 // Default to Unix or Equiv
312 $this->set('PMA_IS_WINDOWS', false);
313 // If PHP_OS is defined then continue
314 if (! defined('PHP_OS')) {
315 return;
318 if (stripos(PHP_OS, 'win') !== false && stripos(PHP_OS, 'darwin') === false) {
319 // Is it some version of Windows
320 $this->set('PMA_IS_WINDOWS', true);
321 } elseif (stripos(PHP_OS, 'OS/2') !== false) {
322 // Is it OS/2 (No file permissions like Windows)
323 $this->set('PMA_IS_WINDOWS', true);
328 * loads configuration from $source, usually the config file
329 * should be called on object creation
331 * @param string|null $source config file
333 * @throws ConfigException
335 public function load(string|null $source = null): bool
337 if ($source !== null) {
338 $this->setSource($source);
341 if (! $this->checkConfigSource()) {
342 return false;
345 /** @var mixed $cfg */
346 $cfg = [];
349 * Parses the configuration file, we throw away any errors or
350 * output.
352 $canUseErrorReporting = function_exists('error_reporting');
353 $oldErrorReporting = null;
354 if ($canUseErrorReporting) {
355 $oldErrorReporting = error_reporting(0);
358 ob_start();
359 try {
360 /** @psalm-suppress UnresolvableInclude */
361 $evalResult = include $this->getSource();
362 } catch (Throwable) {
363 throw new ConfigException('Failed to load phpMyAdmin configuration.');
366 ob_end_clean();
368 if ($canUseErrorReporting) {
369 error_reporting($oldErrorReporting);
372 if ($evalResult === false) {
373 $this->errorConfigFile = true;
374 } else {
375 $this->errorConfigFile = false;
376 $this->sourceMtime = (int) filemtime($this->getSource());
379 if (is_array($cfg)) {
380 $this->config = new Settings($cfg);
383 $this->settings = array_replace_recursive($this->settings, $this->config->asArray());
385 return true;
389 * Sets the connection collation
391 private function setConnectionCollation(): void
393 $collationConnection = $this->get('DefaultConnectionCollation');
394 $dbi = DatabaseInterface::getInstance();
395 if (empty($collationConnection) || $collationConnection === $dbi->getDefaultCollation()) {
396 return;
399 $dbi->setCollation($collationConnection);
403 * Loads user preferences and merges them with current config
404 * must be called after control connection has been established
406 public function loadUserPreferences(ThemeManager $themeManager, bool $isMinimumCommon = false): void
408 $cacheKey = 'server_' . Current::$server;
409 if (Current::$server > 0 && ! $isMinimumCommon) {
410 // cache user preferences, use database only when needed
411 if (
412 ! isset($_SESSION['cache'][$cacheKey]['userprefs'])
413 || $_SESSION['cache'][$cacheKey]['config_mtime'] < $this->sourceMtime
415 $dbi = DatabaseInterface::getInstance();
416 $userPreferences = new UserPreferences($dbi, new Relation($dbi), new Template());
417 $prefs = $userPreferences->load();
418 $_SESSION['cache'][$cacheKey]['userprefs'] = $userPreferences->apply($prefs['config_data']);
419 $_SESSION['cache'][$cacheKey]['userprefs_mtime'] = $prefs['mtime'];
420 $_SESSION['cache'][$cacheKey]['userprefs_type'] = $prefs['type'];
421 $_SESSION['cache'][$cacheKey]['config_mtime'] = $this->sourceMtime;
423 } elseif (Current::$server === 0 || ! isset($_SESSION['cache'][$cacheKey]['userprefs'])) {
424 $this->set('user_preferences', false);
426 return;
429 $configData = $_SESSION['cache'][$cacheKey]['userprefs'];
430 // type is 'db' or 'session'
431 $this->set('user_preferences', $_SESSION['cache'][$cacheKey]['userprefs_type']);
432 $this->set('user_preferences_mtime', $_SESSION['cache'][$cacheKey]['userprefs_mtime']);
434 if (isset($configData['Server']) && is_array($configData['Server'])) {
435 $serverConfig = array_replace_recursive($this->selectedServer, $configData['Server']);
436 $this->selectedServer = (new Server($serverConfig))->asArray();
439 // load config array
440 $this->settings = array_replace_recursive($this->settings, $configData);
441 $this->config = new Settings($this->settings);
443 if ($isMinimumCommon) {
444 return;
447 // settings below start really working on next page load, but
448 // changes are made only in index.php so everything is set when
449 // in frames
451 // save theme
452 if ($themeManager->getThemeCookie() || isset($_REQUEST['set_theme'])) {
453 if (
454 (! isset($configData['ThemeDefault'])
455 && $themeManager->theme->getId() !== 'original')
456 || isset($configData['ThemeDefault'])
457 && $configData['ThemeDefault'] != $themeManager->theme->getId()
459 $this->setUserValue(
460 null,
461 'ThemeDefault',
462 $themeManager->theme->getId(),
463 'original',
466 } elseif (
467 $this->settings['ThemeDefault'] != $themeManager->theme->getId()
468 && $themeManager->checkTheme($this->settings['ThemeDefault'])
470 // no cookie - read default from settings
471 $themeManager->setActiveTheme($this->settings['ThemeDefault']);
472 $themeManager->setThemeCookie();
475 // save language
476 if ($this->issetCookie('pma_lang') || isset($_POST['lang'])) {
477 if (
478 (! isset($configData['lang'])
479 && $GLOBALS['lang'] !== 'en')
480 || isset($configData['lang'])
481 && $GLOBALS['lang'] != $configData['lang']
483 $this->setUserValue(null, 'lang', $GLOBALS['lang'], 'en');
485 } elseif (isset($configData['lang'])) {
486 // read language from settings
487 $language = LanguageManager::getInstance()->getLanguage($configData['lang']);
488 if ($language !== false) {
489 $language->activate();
490 $this->setCookie('pma_lang', $language->getCode());
494 // set connection collation
495 $this->setConnectionCollation();
499 * Sets config value which is stored in user preferences (if available)
500 * or in a cookie.
502 * If user preferences are not yet initialized, option is applied to
503 * global config and added to a update queue, which is processed
504 * by {@link loadUserPreferences()}
506 * @param string|null $cookieName can be null
507 * @param string $cfgPath configuration path
508 * @param mixed $newCfgValue new value
509 * @param string|null $defaultValue default value
511 * @return true|Message
513 public function setUserValue(
514 string|null $cookieName,
515 string $cfgPath,
516 mixed $newCfgValue,
517 string|null $defaultValue = null,
518 ): bool|Message {
519 $dbi = DatabaseInterface::getInstance();
520 $userPreferences = new UserPreferences($dbi, new Relation($dbi), new Template());
521 $result = true;
522 // use permanent user preferences if possible
523 $prefsType = $this->get('user_preferences');
524 if ($prefsType) {
525 if ($defaultValue === null) {
526 $defaultValue = Core::arrayRead($cfgPath, $this->default);
529 $result = $userPreferences->persistOption($cfgPath, $newCfgValue, $defaultValue);
532 if ($prefsType !== 'db' && $cookieName) {
533 // fall back to cookies
534 if ($defaultValue === null) {
535 $defaultValue = Core::arrayRead($cfgPath, $this->settings);
538 $this->setCookie($cookieName, (string) $newCfgValue, $defaultValue);
541 Core::arrayWrite($cfgPath, $this->settings, $newCfgValue);
543 return $result;
547 * Reads value stored by {@link setUserValue()}
549 * @param string $cookieName cookie name
550 * @param mixed $cfgValue config value
552 public function getUserValue(string $cookieName, mixed $cfgValue): mixed
554 $cookieExists = ! empty($this->getCookie($cookieName));
555 $prefsType = $this->get('user_preferences');
556 if ($prefsType === 'db') {
557 // permanent user preferences value exists, remove cookie
558 if ($cookieExists) {
559 $this->removeCookie($cookieName);
561 } elseif ($cookieExists) {
562 return $this->getCookie($cookieName);
565 // return value from $cfg array
566 return $cfgValue;
570 * set source
572 * @param string $source source
574 public function setSource(string $source): void
576 $this->source = trim($source);
579 /** @throws ConfigException */
580 public function checkConfigSource(): bool
582 if ($this->getSource() === '') {
583 // no configuration file set at all
584 return false;
587 if (! @file_exists($this->getSource())) {
588 $this->sourceMtime = 0;
590 return false;
593 if (! @is_readable($this->getSource())) {
594 // manually check if file is readable
595 // might be bug #3059806 Supporting running from CIFS/Samba shares
597 $contents = false;
598 $handle = @fopen($this->getSource(), 'r');
599 if ($handle !== false) {
600 $contents = @fread($handle, 1); // reading 1 byte is enough to test
601 fclose($handle);
604 if ($contents === false) {
605 $this->sourceMtime = 0;
607 throw new ConfigException(sprintf(
608 function_exists('__')
609 ? __('Existing configuration file (%s) is not readable.')
610 : 'Existing configuration file (%s) is not readable.',
611 $this->getSource(),
616 return true;
620 * verifies the permissions on config file (if asked by configuration)
621 * (must be called after config.inc.php has been merged)
623 * @throws ConfigException
625 public function checkPermissions(): void
627 // Check for permissions (on platforms that support it):
628 if (! $this->get('CheckConfigurationPermissions') || ! @file_exists($this->getSource())) {
629 return;
632 $perms = @fileperms($this->getSource());
633 if ($perms === false || ! ($perms & 2)) {
634 return;
637 // This check is normally done after loading configuration
638 $this->checkWebServerOs();
639 if ($this->get('PMA_IS_WINDOWS') === true) {
640 return;
643 $this->sourceMtime = 0;
645 throw new ConfigException(__('Wrong permissions on configuration file, should not be world writable!'));
649 * Checks for errors (must be called after config.inc.php has been merged)
651 * @throws ConfigException
653 public function checkErrors(): void
655 if (! $this->errorConfigFile) {
656 return;
659 $error = '[strong]' . __('Failed to read configuration file!') . '[/strong]'
660 . '[br][br]'
661 . __('This usually means there is a syntax error in it.');
663 throw new ConfigException(Sanitize::convertBBCode($error));
667 * returns specific config setting
669 * @param string $setting config setting
671 * @return mixed|null value
673 public function get(string $setting): mixed
675 return $this->settings[$setting] ?? null;
679 * sets configuration variable
681 * @param string $setting configuration option
682 * @param mixed $value new value for configuration option
684 public function set(string $setting, mixed $value): void
686 if (isset($this->settings[$setting]) && $this->settings[$setting] === $value) {
687 return;
690 $this->settings[$setting] = $value;
691 $this->config = new Settings($this->settings);
695 * returns source for current config
697 * @return string config source
699 public function getSource(): string
701 return $this->source;
705 * checks if upload is enabled
707 public function checkUpload(): void
709 if (! ini_get('file_uploads')) {
710 $this->set('enable_upload', false);
712 return;
715 $this->set('enable_upload', true);
716 // if set "php_admin_value file_uploads Off" in httpd.conf
717 // ini_get() also returns the string "Off" in this case:
718 if (strtolower(ini_get('file_uploads')) !== 'off') {
719 return;
722 $this->set('enable_upload', false);
726 * Maximum upload size as limited by PHP
727 * Used with permission from Moodle (https://moodle.org/) by Martin Dougiamas
729 * this section generates max_upload_size in bytes
731 public function checkUploadSize(): void
733 $fileSize = ini_get('upload_max_filesize');
735 if (! $fileSize) {
736 $fileSize = '5M';
739 $size = Core::getRealSize($fileSize);
740 $postSize = ini_get('post_max_size');
742 if ($postSize) {
743 $size = min($size, Core::getRealSize($postSize));
746 $this->set('max_upload_size', $size);
750 * Checks if protocol is https
752 * This function checks if the https protocol on the active connection.
754 public function isHttps(): bool
756 /** @var mixed $isHttps */
757 $isHttps = $this->get('is_https');
758 if (is_bool($isHttps)) {
759 return $isHttps;
762 $url = $this->get('PmaAbsoluteUri');
764 $isHttps = false;
765 if (! empty($url) && parse_url($url, PHP_URL_SCHEME) === 'https') {
766 $isHttps = true;
767 } elseif (strtolower(Core::getEnv('HTTP_SCHEME')) === 'https') {
768 $isHttps = true;
769 } elseif (strtolower(Core::getEnv('HTTPS')) === 'on') {
770 $isHttps = true;
771 } elseif (stripos(Core::getEnv('REQUEST_URI'), 'https:') === 0) {
772 $isHttps = true;
773 } elseif (strtolower(Core::getEnv('HTTP_HTTPS_FROM_LB')) === 'on') {
774 // A10 Networks load balancer
775 $isHttps = true;
776 } elseif (strtolower(Core::getEnv('HTTP_FRONT_END_HTTPS')) === 'on') {
777 $isHttps = true;
778 } elseif (strtolower(Core::getEnv('HTTP_X_FORWARDED_PROTO')) === 'https') {
779 $isHttps = true;
780 } elseif (strtolower(Core::getEnv('HTTP_CLOUDFRONT_FORWARDED_PROTO')) === 'https') {
781 // Amazon CloudFront, issue #15621
782 $isHttps = true;
783 } elseif (Util::getProtoFromForwardedHeader(Core::getEnv('HTTP_FORWARDED')) === 'https') {
784 // RFC 7239 Forwarded header
785 $isHttps = true;
786 } elseif (Core::getEnv('SERVER_PORT') == 443) {
787 $isHttps = true;
790 $this->set('is_https', $isHttps);
792 return $isHttps;
796 * Get phpMyAdmin root path
798 public function getRootPath(): string
800 $url = $this->get('PmaAbsoluteUri');
802 if (! empty($url)) {
803 $path = parse_url($url, PHP_URL_PATH);
804 if (! empty($path)) {
805 if (! str_ends_with($path, '/')) {
806 return $path . '/';
809 return $path;
813 $parsedUrlPath = Routing::getCleanPathInfo();
815 $parts = explode('/', $parsedUrlPath);
817 /* Remove filename */
818 if (str_ends_with($parts[count($parts) - 1], '.php')) {
819 $parts = array_slice($parts, 0, count($parts) - 1);
822 /* Remove extra path from javascript calls */
823 if (defined('PMA_PATH_TO_BASEDIR')) {
824 $parts = array_slice($parts, 0, count($parts) - 1);
827 // Add one more part to make the path end in slash unless it already ends with slash
828 if (count($parts) < 2 || $parts[array_key_last($parts)] !== '') {
829 $parts[] = '';
832 return implode('/', $parts);
836 * removes cookie
838 * @param string $cookieName name of cookie to remove
840 public function removeCookie(string $cookieName): bool
842 $httpCookieName = $this->getCookieName($cookieName);
844 if ($this->issetCookie($cookieName)) {
845 unset($_COOKIE[$httpCookieName]);
848 if (defined('TESTSUITE')) {
849 return true;
852 return setcookie(
853 $httpCookieName,
855 time() - 3600,
856 $this->getRootPath(),
858 $this->isHttps,
863 * sets cookie if value is different from current cookie value,
864 * or removes if value is equal to default
866 * @param string $cookie name of cookie to remove
867 * @param string $value new cookie value
868 * @param string|null $default default value
869 * @param int|null $validity validity of cookie in seconds (default is one month)
870 * @param bool $httponly whether cookie is only for HTTP (and not for scripts)
872 public function setCookie(
873 string $cookie,
874 string $value,
875 string|null $default = null,
876 int|null $validity = null,
877 bool $httponly = true,
878 ): bool {
879 if ($value !== '' && $value === $default) {
880 // default value is used
881 if ($this->issetCookie($cookie)) {
882 // remove cookie
883 return $this->removeCookie($cookie);
886 return false;
889 if ($value === '' && $this->issetCookie($cookie)) {
890 // remove cookie, value is empty
891 return $this->removeCookie($cookie);
894 $httpCookieName = $this->getCookieName($cookie);
896 if (! $this->issetCookie($cookie) || $this->getCookie($cookie) !== $value) {
897 // set cookie with new value
898 /* Calculate cookie validity */
899 if ($validity === null) {
900 /* Valid for one month */
901 $validity = time() + 2592000;
902 } elseif ($validity === 0) {
903 /* Valid for session */
904 $validity = 0;
905 } else {
906 $validity += time();
909 if (defined('TESTSUITE')) {
910 $_COOKIE[$httpCookieName] = $value;
912 return true;
915 /** @psalm-var 'Lax'|'Strict'|'None' $cookieSameSite */
916 $cookieSameSite = $this->get('CookieSameSite');
918 $optionalParams = [
919 'expires' => $validity,
920 'path' => $this->getRootPath(),
921 'domain' => '',
922 'secure' => $this->isHttps,
923 'httponly' => $httponly,
924 'samesite' => $cookieSameSite,
927 return setcookie($httpCookieName, $value, $optionalParams);
930 // cookie has already $value as value
931 return true;
935 * get cookie
937 * @param string $cookieName The name of the cookie to get
939 * @return mixed|null result of getCookie()
941 public function getCookie(string $cookieName): mixed
943 return $_COOKIE[$this->getCookieName($cookieName)] ?? null;
947 * Get the real cookie name
949 * @param string $cookieName The name of the cookie
951 public function getCookieName(string $cookieName): string
953 return $cookieName . ( $this->isHttps ? '_https' : '' );
957 * isset cookie
959 * @param string $cookieName The name of the cookie to check
961 public function issetCookie(string $cookieName): bool
963 return isset($_COOKIE[$this->getCookieName($cookieName)]);
967 * Wrapper for footer/header rendering
969 * @param string $filename File to check and render
970 * @param string $id Div ID
972 private static function renderCustom(string $filename, string $id): string
974 $retval = '';
975 if (@file_exists($filename)) {
976 $retval .= '<div id="' . $id . '" class="d-print-none">';
977 ob_start();
978 include $filename;
979 $retval .= ob_get_clean();
980 $retval .= '</div>';
983 return $retval;
987 * Renders user configured footer
989 public static function renderFooter(): string
991 return self::renderCustom(CUSTOM_FOOTER_FILE, 'pma_footer');
995 * Renders user configured footer
997 public static function renderHeader(): string
999 return self::renderCustom(CUSTOM_HEADER_FILE, 'pma_header');
1003 * Returns temporary dir path
1005 * @param string $name Directory name
1007 public function getTempDir(string $name): string|null
1009 if (isset(self::$tempDir[$name])) {
1010 return self::$tempDir[$name];
1013 $path = $this->get('TempDir');
1014 if (empty($path)) {
1015 $path = null;
1016 } else {
1017 $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name;
1018 if (! @is_dir($path)) {
1019 @mkdir($path, 0770, true);
1022 if (! @is_dir($path) || ! @is_writable($path)) {
1023 $path = null;
1027 self::$tempDir[$name] = $path;
1029 return $path;
1033 * Returns temporary directory
1035 public function getUploadTempDir(): string|null
1037 // First try configured temp dir
1038 // Fallback to PHP upload_tmp_dir
1039 $dirs = [$this->getTempDir('upload'), ini_get('upload_tmp_dir'), sys_get_temp_dir()];
1041 foreach ($dirs as $dir) {
1042 if (! empty($dir) && @is_writable($dir)) {
1043 return realpath($dir);
1047 return null;
1050 /** @return int<0, max> */
1051 public function selectServer(mixed $serverParamFromRequest): int
1053 $serverNumber = 0;
1054 if (is_numeric($serverParamFromRequest)) {
1055 $serverNumber = (int) $serverParamFromRequest;
1056 $serverNumber = $serverNumber >= 1 ? $serverNumber : 0;
1057 } elseif (is_string($serverParamFromRequest) && $serverParamFromRequest !== '') {
1058 /** Lookup server by name (see FAQ 4.8) */
1059 foreach ($this->config->Servers as $i => $server) {
1060 if ($server->host === $serverParamFromRequest || $server->verbose === $serverParamFromRequest) {
1061 $serverNumber = $i;
1062 break;
1065 $verboseToLower = mb_strtolower($server->verbose);
1066 $serverToLower = mb_strtolower($serverParamFromRequest);
1067 if ($verboseToLower === $serverToLower || md5($verboseToLower) === $serverToLower) {
1068 $serverNumber = $i;
1069 break;
1075 * If no server is selected, make sure that $this->settings['Server'] is empty (so
1076 * that nothing will work), and skip server authentication.
1077 * We do NOT exit here, but continue on without logging into any server.
1078 * This way, the welcome page will still come up (with no server info) and
1079 * present a choice of servers in the case that there are multiple servers
1080 * and '$this->settings['ServerDefault'] = 0' is set.
1082 if (isset($this->config->Servers[$serverNumber])) {
1083 $this->hasSelectedServer = true;
1084 $this->selectedServer = $this->config->Servers[$serverNumber]->asArray();
1085 $this->settings['Server'] = $this->selectedServer;
1086 } elseif (isset($this->config->Servers[$this->config->ServerDefault])) {
1087 $this->hasSelectedServer = true;
1088 $serverNumber = $this->config->ServerDefault;
1089 $this->selectedServer = $this->config->Servers[$this->config->ServerDefault]->asArray();
1090 $this->settings['Server'] = $this->selectedServer;
1091 } else {
1092 $this->hasSelectedServer = false;
1093 $serverNumber = 0;
1094 $this->selectedServer = (new Server())->asArray();
1095 $this->settings['Server'] = [];
1098 $this->server = $serverNumber;
1100 return $this->server;
1104 * Return connection parameters for the database server
1106 public static function getConnectionParams(Server $currentServer, ConnectionType $connectionType): Server
1108 if ($connectionType !== ConnectionType::ControlUser) {
1109 if ($currentServer->host !== '' && $currentServer->port !== '') {
1110 return $currentServer;
1113 $server = $currentServer->asArray();
1114 $server['host'] = $server['host'] === '' ? 'localhost' : $server['host'];
1115 $server['port'] = $server['port'] === '' ? '0' : $server['port'];
1117 return new Server($server);
1120 $server = [
1121 'user' => $currentServer->controlUser,
1122 'password' => $currentServer->controlPass,
1123 'host' => $currentServer->controlHost !== '' ? $currentServer->controlHost : $currentServer->host,
1124 'port' => '0',
1125 'socket' => null,
1126 'compress' => null,
1127 'ssl' => null,
1128 'ssl_key' => null,
1129 'ssl_cert' => null,
1130 'ssl_ca' => null,
1131 'ssl_ca_path' => null,
1132 'ssl_ciphers' => null,
1133 'ssl_verify' => null,
1134 'hide_connection_errors' => null,
1137 // Share the settings if the host is same
1138 if ($server['host'] === $currentServer->host) {
1139 $server['port'] = $currentServer->port !== '' ? $currentServer->port : '0';
1140 $server['socket'] = $currentServer->socket;
1141 $server['compress'] = $currentServer->compress;
1142 $server['ssl'] = $currentServer->ssl;
1143 $server['ssl_key'] = $currentServer->sslKey;
1144 $server['ssl_cert'] = $currentServer->sslCert;
1145 $server['ssl_ca'] = $currentServer->sslCa;
1146 $server['ssl_ca_path'] = $currentServer->sslCaPath;
1147 $server['ssl_ciphers'] = $currentServer->sslCiphers;
1148 $server['ssl_verify'] = $currentServer->sslVerify;
1149 $server['hide_connection_errors'] = $currentServer->hideConnectionErrors;
1152 // Set configured port
1153 if ($currentServer->controlPort !== '') {
1154 $server['port'] = $currentServer->controlPort;
1157 // Set any configuration with control_ prefix
1158 $server['socket'] = $currentServer->controlSocket ?? $server['socket'];
1159 $server['compress'] = $currentServer->controlCompress ?? $server['compress'];
1160 $server['ssl'] = $currentServer->controlSsl ?? $server['ssl'];
1161 $server['ssl_key'] = $currentServer->controlSslKey ?? $server['ssl_key'];
1162 $server['ssl_cert'] = $currentServer->controlSslCert ?? $server['ssl_cert'];
1163 $server['ssl_ca'] = $currentServer->controlSslCa ?? $server['ssl_ca'];
1164 $server['ssl_ca_path'] = $currentServer->controlSslCaPath ?? $server['ssl_ca_path'];
1165 $server['ssl_ciphers'] = $currentServer->controlSslCiphers ?? $server['ssl_ciphers'];
1166 $server['ssl_verify'] = $currentServer->controlSslVerify ?? $server['ssl_verify'];
1167 $server['hide_connection_errors'] = $currentServer->controlHideConnectionErrors
1168 ?? $server['hide_connection_errors'];
1170 if ($server['host'] === '') {
1171 $server['host'] = 'localhost';
1174 return new Server($server);
1178 * Get LoginCookieValidity from preferences cache.
1180 * No generic solution for loading preferences from cache as some settings
1181 * need to be kept for processing in loadUserPreferences().
1183 * @see loadUserPreferences()
1185 public function getLoginCookieValidityFromCache(int $server): void
1187 $cacheKey = 'server_' . $server;
1189 if (! isset($_SESSION['cache'][$cacheKey]['userprefs']['LoginCookieValidity'])) {
1190 return;
1193 $value = $_SESSION['cache'][$cacheKey]['userprefs']['LoginCookieValidity'];
1194 $this->set('LoginCookieValidity', $value);
1195 $this->settings['LoginCookieValidity'] = $value;
1198 public function getSettings(): Settings
1200 return $this->config;
1203 public function hasSelectedServer(): bool
1205 return $this->hasSelectedServer;
1208 public function getChangeLogFilePath(): string
1210 return CHANGELOG_FILE;