/
var
/
www
/
html
/
restaurants
/
var
/
phpmyadmin
/
src
/
Config
/
Upload File
HOME
<?php /** * Config file management */ declare(strict_types=1); namespace PhpMyAdmin\Config; use PhpMyAdmin\Config; use PhpMyAdmin\Core; use PhpMyAdmin\Current; use function __; use function _pgettext; use function array_diff; use function array_flip; use function array_keys; use function array_merge; use function count; use function is_array; use function preg_replace; /** * Config file management class. * Stores its data in $_SESSION */ class ConfigFile { /** * Stores default phpMyAdmin config * * @see Settings * * @var mixed[] */ private array $defaultCfg; /** * Stores allowed values for non-standard fields * * @var array<string, string|mixed[]> */ private array $cfgDb; /** * Whether we are currently working in PMA Setup context */ private bool $isInSetup; /** * Keys which will be always written to config file * * @var mixed[] */ private array $persistKeys = []; /** * Changes keys while updating config in {@link updateWithGlobalConfig()} * or reading by {@link getConfig()} or {@link getConfigArray()} * * @var mixed[] */ private array $cfgUpdateReadMapping = []; /** * Key filter for {@link set()} */ private array|null $setFilter = null; /** * Instance id (key in $_SESSION array, separate for each server - * ConfigFile{server id}) */ private string $id; /** * @param mixed[]|null $baseConfig base configuration read from {@link PhpMyAdmin\Config::$base_config}, use only when not in PMA Setup Stores original PMA config, not modified by user preferences */ public function __construct(private array|null $baseConfig = null) { // load default config values $settings = new Settings([]); $this->defaultCfg = $settings->asArray(); // load additional config information $this->cfgDb = $this->getAllowedValues(); $this->isInSetup = $baseConfig === null; $this->id = 'ConfigFile' . Current::$server; if (isset($_SESSION[$this->id])) { return; } $_SESSION[$this->id] = []; } /** * Sets names of config options which will be placed in config file even if * they are set to their default values (use only full paths) * * @param mixed[] $keys the names of the config options */ public function setPersistKeys(array $keys): void { // checking key presence is much faster than searching so move values // to keys $this->persistKeys = array_flip($keys); } /** * Returns flipped array set by {@link setPersistKeys()} * * @return mixed[] */ public function getPersistKeysMap(): array { return $this->persistKeys; } /** * By default ConfigFile allows setting of all configuration keys, use * this method to set up a filter on {@link set()} method * * @param mixed[]|null $keys array of allowed keys or null to remove filter */ public function setAllowedKeys(array|null $keys): void { if ($keys === null) { $this->setFilter = null; return; } // checking key presence is much faster than searching so move values // to keys $this->setFilter = array_flip($keys); } /** * Sets path mapping for updating config in * {@link updateWithGlobalConfig()} or reading * by {@link getConfig()} or {@link getConfigArray()} * * @param mixed[] $mapping Contains the mapping of "Server/config options" * to "Server/1/config options" */ public function setCfgUpdateReadMapping(array $mapping): void { $this->cfgUpdateReadMapping = $mapping; } /** * Resets configuration data */ public function resetConfigData(): void { $_SESSION[$this->id] = []; } /** * Sets configuration data (overrides old data) * * @param mixed[] $cfg Configuration options */ public function setConfigData(array $cfg): void { $_SESSION[$this->id] = $cfg; } /** * Sets config value */ public function set(string $path, mixed $value, string|null $canonicalPath = null): void { if ($canonicalPath === null) { $canonicalPath = $this->getCanonicalPath($path); } if ($this->setFilter !== null && ! isset($this->setFilter[$canonicalPath])) { return; } // if the path isn't protected it may be removed if (isset($this->persistKeys[$canonicalPath])) { Core::arrayWrite($path, $_SESSION[$this->id], $value); return; } $defaultValue = $this->getDefault($canonicalPath); if ($this->isInSetup) { // remove if it has a default value or is empty $removePath = $value === $defaultValue || empty($value) && empty($defaultValue); } else { // get original config values not overwritten by user // preferences to allow for overwriting options set in // config.inc.php with default values $instanceDefaultValue = Core::arrayRead($canonicalPath, $this->baseConfig); // remove if it has a default value and base config (config.inc.php) // uses default value $removePath = $value === $defaultValue && $instanceDefaultValue === $defaultValue; } if ($removePath) { Core::arrayRemove($path, $_SESSION[$this->id]); return; } Core::arrayWrite($path, $_SESSION[$this->id], $value); } /** * Flattens multidimensional array, changes indices to paths * (eg. 'key/subkey'). * * @param mixed[] $array Multidimensional array * @param string $prefix Prefix * * @return mixed[] */ private function getFlatArray(array $array, string $prefix = ''): array { $result = []; foreach ($array as $key => $value) { if (is_array($value) && ! isset($value[0])) { $result += $this->getFlatArray($value, $prefix . $key . '/'); } else { $result[$prefix . $key] = $value; } } return $result; } /** * Returns default config in a flattened array * * @return mixed[] */ public function getFlatDefaultConfig(): array { return $this->getFlatArray($this->defaultCfg); } /** * Updates config with values read from given array * (config will contain differences to defaults from {@see \PhpMyAdmin\Config\Settings}). * * @param mixed[] $cfg Configuration */ public function updateWithGlobalConfig(array $cfg): void { // load config array and flatten it $flatConfig = $this->getFlatArray($cfg); // save values map for translating a few user preferences paths, // should be complemented by code reading from generated config // to perform inverse mapping foreach ($flatConfig as $path => $value) { if (isset($this->cfgUpdateReadMapping[$path])) { $path = $this->cfgUpdateReadMapping[$path]; } $this->set($path, $value, $path); } } /** * Returns config value or $default if it's not set * * @param string $path Path of config file * @param mixed $default Default values */ public function get(string $path, mixed $default = null): mixed { return Core::arrayRead($path, $_SESSION[$this->id], $default); } /** * Returns default config value or $default it it's not set ie. it doesn't * exist in {@see \PhpMyAdmin\Config\Settings} ($cfg). * * @param string $canonicalPath Canonical path * @param mixed $default Default value */ public function getDefault(string $canonicalPath, mixed $default = null): mixed { return Core::arrayRead($canonicalPath, $this->defaultCfg, $default); } /** * Returns config value, if it's not set uses the default one; returns * $default if the path isn't set and doesn't contain a default value * * @param string $path Path * @param mixed $default Default value */ public function getValue(string $path, mixed $default = null): mixed { $v = Core::arrayRead($path, $_SESSION[$this->id]); if ($v !== null) { return $v; } $path = $this->getCanonicalPath($path); return $this->getDefault($path, $default); } /** * Returns canonical path * * @param string $path Path */ public function getCanonicalPath(string $path): string { return preg_replace('#^Servers/([\d]+)/#', 'Servers/1/', $path); } /** * Returns config database entry for $path * * @param string $path path of the variable in config db * @param mixed $default default value */ public function getDbEntry(string $path, mixed $default = null): mixed { return Core::arrayRead($path, $this->cfgDb, $default); } /** * Returns server count */ public function getServerCount(): int { return isset($_SESSION[$this->id]['Servers']) ? count($_SESSION[$this->id]['Servers']) : 0; } /** * Returns server list * * @return mixed[] */ public function getServers(): array { return $_SESSION[$this->id]['Servers'] ?? []; } /** * Returns DSN of given server * * @param int $server server index */ public function getServerDSN(int $server): string { if (! isset($_SESSION[$this->id]['Servers'][$server])) { return ''; } $path = 'Servers/' . $server; $dsn = 'mysqli://'; if ($this->getValue($path . '/auth_type') === 'config') { $dsn .= $this->getValue($path . '/user'); if (! empty($this->getValue($path . '/password'))) { $dsn .= ':***'; } $dsn .= '@'; } if ($this->getValue($path . '/host') !== 'localhost') { $dsn .= $this->getValue($path . '/host'); $port = $this->getValue($path . '/port'); if ($port) { $dsn .= ':' . $port; } } else { $dsn .= $this->getValue($path . '/socket'); } return $dsn; } /** * Returns server name * * @param int $id server index */ public function getServerName(int $id): string { if (! isset($_SESSION[$this->id]['Servers'][$id])) { return ''; } $verbose = $this->get('Servers/' . $id . '/verbose'); if (! empty($verbose)) { return $verbose; } $host = $this->get('Servers/' . $id . '/host'); return empty($host) ? 'localhost' : $host; } /** * Removes server * * @param int $server server index */ public function removeServer(int $server): void { if (! isset($_SESSION[$this->id]['Servers'][$server])) { return; } $lastServer = $this->getServerCount(); /** @infection-ignore-all */ for ($i = $server; $i < $lastServer; $i++) { $_SESSION[$this->id]['Servers'][$i] = $_SESSION[$this->id]['Servers'][$i + 1]; } unset($_SESSION[$this->id]['Servers'][$lastServer]); if (! isset($_SESSION[$this->id]['ServerDefault']) || $_SESSION[$this->id]['ServerDefault'] != $lastServer) { return; } unset($_SESSION[$this->id]['ServerDefault']); } /** * Returns configuration array (full, multidimensional format) * * @return mixed[] */ public function getConfig(): array { $c = $_SESSION[$this->id]; foreach ($this->cfgUpdateReadMapping as $mapTo => $mapFrom) { // if the key $c exists in $map_to if (Core::arrayRead($mapTo, $c) === null) { continue; } Core::arrayWrite($mapTo, $c, Core::arrayRead($mapFrom, $c)); Core::arrayRemove($mapFrom, $c); } return $c; } /** * Returns configuration array (flat format) * * @return mixed[] */ public function getConfigArray(): array { $c = $this->getFlatArray($_SESSION[$this->id]); $persistKeys = array_diff( array_keys($this->persistKeys), array_keys($c), ); foreach ($persistKeys as $k) { $c[$k] = $this->getDefault($this->getCanonicalPath($k)); } foreach ($this->cfgUpdateReadMapping as $mapTo => $mapFrom) { if (! isset($c[$mapFrom])) { continue; } $c[$mapTo] = $c[$mapFrom]; unset($c[$mapFrom]); } return $c; } /** * Database with allowed values for configuration stored in the $cfg array, * used by setup script and user preferences to generate forms. * * Value meaning: * array - select field, array contains allowed values * string - type override * * @return array<string, string|mixed[]> */ public function getAllowedValues(): array { $config = Config::getInstance(); return [ 'Servers' => [ 1 => [ 'port' => 'integer', 'auth_type' => ['config', 'http', 'signon', 'cookie'], 'AllowDeny' => ['order' => ['', 'deny,allow', 'allow,deny', 'explicit']], 'only_db' => 'array', ], ], 'RecodingEngine' => ['auto', 'iconv', 'mb', 'none'], 'OBGzip' => ['auto', true, false], 'MemoryLimit' => 'short_string', 'NavigationLogoLinkWindow' => ['main', 'new'], 'NavigationTreeDefaultTabTable' => [ // fields list 'structure' => __('Structure'), // SQL form 'sql' => __('SQL'), // search page 'search' => __('Search'), // insert row page 'insert' => __('Insert'), // browse page 'browse' => __('Browse'), ], 'NavigationTreeDefaultTabTable2' => [ //don't display '' => '', // fields list 'structure' => __('Structure'), // SQL form 'sql' => __('SQL'), // search page 'search' => __('Search'), // insert row page 'insert' => __('Insert'), // browse page 'browse' => __('Browse'), ], 'NavigationTreeDbSeparator' => 'short_string', 'NavigationTreeTableSeparator' => 'short_string', 'NavigationWidth' => 'integer', 'TableNavigationLinksMode' => ['icons' => __('Icons'), 'text' => __('Text'), 'both' => __('Both')], 'MaxRows' => [25, 50, 100, 250, 500], 'Order' => ['ASC', 'DESC', 'SMART'], 'RowActionLinks' => [ 'none' => __('Nowhere'), 'left' => __('Left'), 'right' => __('Right'), 'both' => __('Both'), ], 'TablePrimaryKeyOrder' => ['NONE' => __('None'), 'ASC' => __('Ascending'), 'DESC' => __('Descending')], 'ProtectBinary' => [false, 'blob', 'noblob', 'all'], 'CharEditing' => ['input', 'textarea'], 'TabsMode' => ['icons' => __('Icons'), 'text' => __('Text'), 'both' => __('Both')], 'PDFDefaultPageSize' => [ 'A3' => 'A3', 'A4' => 'A4', 'A5' => 'A5', 'letter' => 'letter', 'legal' => 'legal', ], 'ActionLinksMode' => ['icons' => __('Icons'), 'text' => __('Text'), 'both' => __('Both')], 'GridEditing' => [ 'click' => __('Click'), 'double-click' => __('Double click'), 'disabled' => __('Disabled'), ], 'RelationalDisplay' => ['K' => __('key'), 'D' => __('display column')], 'DefaultTabServer' => [ // the welcome page (recommended for multiuser setups) 'welcome' => __('Welcome'), // list of databases 'databases' => __('Databases'), // runtime information 'status' => __('Status'), // MySQL server variables 'variables' => __('Variables'), // user management 'privileges' => __('Privileges'), ], 'DefaultTabDatabase' => [ // tables list 'structure' => __('Structure'), // SQL form 'sql' => __('SQL'), // search query 'search' => __('Search'), // operations on database 'operations' => __('Operations'), ], 'DefaultTabTable' => [ // fields list 'structure' => __('Structure'), // SQL form 'sql' => __('SQL'), // search page 'search' => __('Search'), // insert row page 'insert' => __('Insert'), // browse page 'browse' => __('Browse'), ], 'InitialSlidersState' => ['open' => __('Open'), 'closed' => __('Closed'), 'disabled' => __('Disabled')], 'FirstDayOfCalendar' => [ 1 => _pgettext('Week day name', 'Monday'), 2 => _pgettext('Week day name', 'Tuesday'), 3 => _pgettext('Week day name', 'Wednesday'), 4 => _pgettext('Week day name', 'Thursday'), 5 => _pgettext('Week day name', 'Friday'), 6 => _pgettext('Week day name', 'Saturday'), 7 => _pgettext('Week day name', 'Sunday'), ], 'SendErrorReports' => [ 'ask' => __('Ask before sending error reports'), 'always' => __('Always send error reports'), 'never' => __('Never send error reports'), ], 'DefaultForeignKeyChecks' => [ 'default' => __('Server default'), 'enable' => __('Enable'), 'disable' => __('Disable'), ], 'Import' => [ 'format' => [ // CSV 'csv', // DocSQL 'docsql', // CSV using LOAD DATA 'ldi', // SQL 'sql', ], 'charset' => array_merge([''], $config->settings['AvailableCharsets'] ?? []), 'sql_compatibility' => [ 'NONE', 'ANSI', 'DB2', 'MAXDB', 'MYSQL323', 'MYSQL40', 'MSSQL', 'ORACLE', // removed; in MySQL 5.0.33, this produces exports that // can't be read by POSTGRESQL (see our bug #1596328) //'POSTGRESQL', 'TRADITIONAL', ], 'csv_terminated' => 'short_string', 'csv_enclosed' => 'short_string', 'csv_escaped' => 'short_string', 'ldi_terminated' => 'short_string', 'ldi_enclosed' => 'short_string', 'ldi_escaped' => 'short_string', 'ldi_local_option' => ['auto', true, false], ], 'Export' => [ '_sod_select' => [ 'structure' => __('structure'), 'data' => __('data'), 'structure_and_data' => __('structure and data'), ], 'method' => [ 'quick' => __('Quick - display only the minimal options to configure'), 'custom' => __('Custom - display all possible options to configure'), 'custom-no-form' => __('Custom - like above, but without the quick/custom choice'), ], 'format' => [ 'codegen', 'csv', 'excel', 'htmlexcel', 'htmlword', 'latex', 'ods', 'odt', 'pdf', 'sql', 'texytext', 'xml', 'yaml', ], 'compression' => ['none', 'zip', 'gzip'], 'charset' => array_merge([''], $config->settings['AvailableCharsets'] ?? []), 'sql_compatibility' => [ 'NONE', 'ANSI', 'DB2', 'MAXDB', 'MYSQL323', 'MYSQL40', 'MSSQL', 'ORACLE', // removed; in MySQL 5.0.33, this produces exports that // can't be read by POSTGRESQL (see our bug #1596328) //'POSTGRESQL', 'TRADITIONAL', ], 'codegen_format' => ['#', 'NHibernate C# DO', 'NHibernate XML'], 'csv_separator' => 'short_string', 'csv_terminated' => 'short_string', 'csv_enclosed' => 'short_string', 'csv_escaped' => 'short_string', 'csv_null' => 'short_string', 'excel_null' => 'short_string', 'excel_edition' => [ 'win' => 'Windows', 'mac_excel2003' => 'Excel 2003 / Macintosh', 'mac_excel2008' => 'Excel 2008 / Macintosh', ], 'sql_structure_or_data' => [ 'structure' => __('structure'), 'data' => __('data'), 'structure_and_data' => __('structure and data'), ], 'sql_type' => ['INSERT', 'UPDATE', 'REPLACE'], 'sql_insert_syntax' => [ 'complete' => __('complete inserts'), 'extended' => __('extended inserts'), 'both' => __('both of the above'), 'none' => __('neither of the above'), ], 'htmlword_structure_or_data' => [ 'structure' => __('structure'), 'data' => __('data'), 'structure_and_data' => __('structure and data'), ], 'htmlword_null' => 'short_string', 'ods_null' => 'short_string', 'odt_null' => 'short_string', 'odt_structure_or_data' => [ 'structure' => __('structure'), 'data' => __('data'), 'structure_and_data' => __('structure and data'), ], 'texytext_structure_or_data' => [ 'structure' => __('structure'), 'data' => __('data'), 'structure_and_data' => __('structure and data'), ], 'texytext_null' => 'short_string', ], 'Console' => [ 'Mode' => ['info', 'show', 'collapse'], 'OrderBy' => ['exec', 'time', 'count'], 'Order' => ['asc', 'desc'], ], /** * Basic validator assignments (functions from libraries/config/Validator.php * and 'window.validators' object in js/config.js) * Use only full paths and form ids */ '_validators' => [ 'Console/Height' => 'validateNonNegativeNumber', 'CharTextareaCols' => 'validatePositiveNumber', 'CharTextareaRows' => 'validatePositiveNumber', 'ExecTimeLimit' => 'validateNonNegativeNumber', 'Export/sql_max_query_size' => 'validatePositiveNumber', 'FirstLevelNavigationItems' => 'validatePositiveNumber', 'ForeignKeyMaxLimit' => 'validatePositiveNumber', 'Import/csv_enclosed' => [['validateByRegex', '/^.?$/']], 'Import/csv_escaped' => [['validateByRegex', '/^.$/']], 'Import/csv_terminated' => [['validateByRegex', '/^.$/']], 'Import/ldi_enclosed' => [['validateByRegex', '/^.?$/']], 'Import/ldi_escaped' => [['validateByRegex', '/^.$/']], 'Import/ldi_terminated' => [['validateByRegex', '/^.$/']], 'Import/skip_queries' => 'validateNonNegativeNumber', 'InsertRows' => 'validatePositiveNumber', 'NumRecentTables' => 'validateNonNegativeNumber', 'NumFavoriteTables' => 'validateNonNegativeNumber', 'LimitChars' => 'validatePositiveNumber', 'LoginCookieValidity' => 'validatePositiveNumber', 'LoginCookieStore' => 'validateNonNegativeNumber', 'MaxDbList' => 'validatePositiveNumber', 'MaxNavigationItems' => 'validatePositiveNumber', 'MaxCharactersInDisplayedSQL' => 'validatePositiveNumber', 'MaxRows' => 'validatePositiveNumber', 'MaxSizeForInputField' => 'validatePositiveNumber', 'MinSizeForInputField' => 'validateNonNegativeNumber', 'MaxTableList' => 'validatePositiveNumber', 'MemoryLimit' => [['validateByRegex', '/^(-1|(\d+(?:[kmg])?))$/i']], 'NavigationTreeDisplayItemFilterMinimum' => 'validatePositiveNumber', 'NavigationTreeTableLevel' => 'validatePositiveNumber', 'NavigationWidth' => 'validateNonNegativeNumber', 'QueryHistoryMax' => 'validatePositiveNumber', 'RepeatCells' => 'validateNonNegativeNumber', 'Server' => 'validateServer', 'Server_pmadb' => 'validatePMAStorage', 'Servers/1/port' => 'validatePortNumber', 'Servers/1/hide_db' => 'validateRegex', 'TextareaCols' => 'validatePositiveNumber', 'TextareaRows' => 'validatePositiveNumber', 'TrustedProxies' => 'validateTrustedProxies', ], /** * Additional validators used for user preferences */ '_userValidators' => [ 'MaxDbList' => [['validateUpperBound', 'value:MaxDbList']], 'MaxTableList' => [['validateUpperBound', 'value:MaxTableList']], 'QueryHistoryMax' => [['validateUpperBound', 'value:QueryHistoryMax']], ], ]; } }