diff options
| author | Charles <sircharlesaze@gmail.com> | 2020-01-09 10:55:03 +0100 |
|---|---|---|
| committer | Charles <sircharlesaze@gmail.com> | 2020-01-09 13:09:38 +0100 |
| commit | 04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa (patch) | |
| tree | 5c691241355c943a3c68ddb06b8cf8c60aa11319 /srcs/phpmyadmin/libraries/classes/Config | |
| parent | 7e0d85db834d6351ed85d01e5126ac31dc510b86 (diff) | |
| download | ft_server-04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa.tar.gz ft_server-04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa.tar.bz2 ft_server-04d6d5ca99ebfd1cebb8ce06618fb3811fc1a8aa.zip | |
phpmyadmin working
Diffstat (limited to 'srcs/phpmyadmin/libraries/classes/Config')
36 files changed, 6473 insertions, 0 deletions
diff --git a/srcs/phpmyadmin/libraries/classes/Config/ConfigFile.php b/srcs/phpmyadmin/libraries/classes/Config/ConfigFile.php new file mode 100644 index 0000000..1e053dd --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/ConfigFile.php @@ -0,0 +1,531 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Config file management + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Config; +use PhpMyAdmin\Core; + +/** + * Config file management class. + * Stores its data in $_SESSION + * + * @package PhpMyAdmin + */ +class ConfigFile +{ + /** + * Stores default PMA config from config.default.php + * @var array + */ + private $_defaultCfg; + + /** + * Stores allowed values for non-standard fields + * @var array + */ + private $_cfgDb; + + /** + * Stores original PMA config, not modified by user preferences + * @var array|null + */ + private $_baseCfg; + + /** + * Whether we are currently working in PMA Setup context + * @var bool + */ + private $_isInSetup; + + /** + * Keys which will be always written to config file + * @var array + */ + private $_persistKeys = []; + + /** + * Changes keys while updating config in {@link updateWithGlobalConfig()} + * or reading by {@link getConfig()} or {@link getConfigArray()} + * @var array + */ + private $_cfgUpdateReadMapping = []; + + /** + * Key filter for {@link set()} + * @var array|null + */ + private $_setFilter; + + /** + * Instance id (key in $_SESSION array, separate for each server - + * ConfigFile{server id}) + * @var string + */ + private $_id; + + /** + * Result for {@link _flattenArray()} + * @var array|null + */ + private $_flattenArrayResult; + + /** + * Constructor + * + * @param array|null $baseConfig base configuration read from + * {@link PhpMyAdmin\Config::$base_config}, + * use only when not in PMA Setup + */ + public function __construct($baseConfig = null) + { + // load default config values + $cfg = &$this->_defaultCfg; + include ROOT_PATH . 'libraries/config.default.php'; + + // load additional config information + $this->_cfgDb = include ROOT_PATH . 'libraries/config.values.php'; + + // apply default values overrides + if (count($this->_cfgDb['_overrides'])) { + foreach ($this->_cfgDb['_overrides'] as $path => $value) { + Core::arrayWrite($path, $cfg, $value); + } + } + + $this->_baseCfg = $baseConfig; + $this->_isInSetup = $baseConfig === null; + $this->_id = 'ConfigFile' . $GLOBALS['server']; + if (! isset($_SESSION[$this->_id])) { + $_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 array $keys the names of the config options + * + * @return void + */ + public function setPersistKeys(array $keys) + { + // 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 array + */ + public function getPersistKeysMap() + { + 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 array|null $keys array of allowed keys or null to remove filter + * + * @return void + */ + public function setAllowedKeys($keys) + { + 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 array $mapping Contains the mapping of "Server/config options" + * to "Server/1/config options" + * + * @return void + */ + public function setCfgUpdateReadMapping(array $mapping) + { + $this->_cfgUpdateReadMapping = $mapping; + } + + /** + * Resets configuration data + * + * @return void + */ + public function resetConfigData() + { + $_SESSION[$this->_id] = []; + } + + /** + * Sets configuration data (overrides old data) + * + * @param array $cfg Configuration options + * + * @return void + */ + public function setConfigData(array $cfg) + { + $_SESSION[$this->_id] = $cfg; + } + + /** + * Sets config value + * + * @param string $path Path + * @param mixed $value Value + * @param string $canonicalPath Canonical path + * + * @return void + */ + public function set($path, $value, $canonicalPath = null) + { + if ($canonicalPath === null) { + $canonicalPath = $this->getCanonicalPath($path); + } + // apply key whitelist + 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); + $removePath = $value === $defaultValue; + if ($this->_isInSetup) { + // remove if it has a default value or is empty + $removePath = $removePath + || (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->_baseCfg + ); + // remove if it has a default value and base config (config.inc.php) + // uses default value + $removePath = $removePath + && ($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'). + * Used as array_walk() callback. + * + * @param mixed $value Value + * @param mixed $key Key + * @param mixed $prefix Prefix + * + * @return void + */ + private function _flattenArray($value, $key, $prefix) + { + // no recursion for numeric arrays + if (is_array($value) && ! isset($value[0])) { + $prefix .= $key . '/'; + array_walk($value, [$this, '_flattenArray'], $prefix); + } else { + $this->_flattenArrayResult[$prefix . $key] = $value; + } + } + + /** + * Returns default config in a flattened array + * + * @return array + */ + public function getFlatDefaultConfig() + { + $this->_flattenArrayResult = []; + array_walk($this->_defaultCfg, [$this, '_flattenArray'], ''); + $flatConfig = $this->_flattenArrayResult; + $this->_flattenArrayResult = null; + return $flatConfig; + } + + /** + * Updates config with values read from given array + * (config will contain differences to defaults from config.defaults.php). + * + * @param array $cfg Configuration + * + * @return void + */ + public function updateWithGlobalConfig(array $cfg) + { + // load config array and flatten it + $this->_flattenArrayResult = []; + array_walk($cfg, [$this, '_flattenArray'], ''); + $flatConfig = $this->_flattenArrayResult; + $this->_flattenArrayResult = null; + + // 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 + * + * @return mixed + */ + public function get($path, $default = null) + { + 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 config.default.php ($cfg) and config.values.php + * ($_cfg_db['_overrides']) + * + * @param string $canonicalPath Canonical path + * @param mixed $default Default value + * + * @return mixed + */ + public function getDefault($canonicalPath, $default = null) + { + 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 + * + * @return mixed + */ + public function getValue($path, $default = null) + { + $v = Core::arrayRead($path, $_SESSION[$this->_id], null); + if ($v !== null) { + return $v; + } + $path = $this->getCanonicalPath($path); + return $this->getDefault($path, $default); + } + + /** + * Returns canonical path + * + * @param string $path Path + * + * @return string + */ + public function getCanonicalPath($path) + { + 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 + * + * @return mixed + */ + public function getDbEntry($path, $default = null) + { + return Core::arrayRead($path, $this->_cfgDb, $default); + } + + /** + * Returns server count + * + * @return int + */ + public function getServerCount() + { + return isset($_SESSION[$this->_id]['Servers']) + ? count($_SESSION[$this->_id]['Servers']) + : 0; + } + + /** + * Returns server list + * + * @return array|null + */ + public function getServers() + { + return isset($_SESSION[$this->_id]['Servers']) + ? $_SESSION[$this->_id]['Servers'] + : null; + } + + /** + * Returns DSN of given server + * + * @param integer $server server index + * + * @return string + */ + public function getServerDSN($server) + { + 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 + * + * @return string + */ + public function getServerName($id) + { + 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 + * + * @return void + */ + public function removeServer($server) + { + if (! isset($_SESSION[$this->_id]['Servers'][$server])) { + return; + } + $lastServer = $this->getServerCount(); + + 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 + ) { + unset($_SESSION[$this->_id]['ServerDefault']); + } + } + + /** + * Returns configuration array (full, multidimensional format) + * + * @return array + */ + public function getConfig() + { + $c = $_SESSION[$this->_id]; + foreach ($this->_cfgUpdateReadMapping as $mapTo => $mapFrom) { + // if the key $c exists in $map_to + if (Core::arrayRead($mapTo, $c) !== null) { + Core::arrayWrite($mapTo, $c, Core::arrayRead($mapFrom, $c)); + Core::arrayRemove($mapFrom, $c); + } + } + return $c; + } + + /** + * Returns configuration array (flat format) + * + * @return array + */ + public function getConfigArray() + { + $this->_flattenArrayResult = []; + array_walk($_SESSION[$this->_id], [$this, '_flattenArray'], ''); + $c = $this->_flattenArrayResult; + $this->_flattenArrayResult = null; + + $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; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Descriptions.php b/srcs/phpmyadmin/libraries/classes/Config/Descriptions.php new file mode 100644 index 0000000..22e7c1f --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Descriptions.php @@ -0,0 +1,934 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Verbose descriptions for settings. + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Sanitize; + +/** + * Base class for forms, loads default configuration options, checks allowed + * values etc. + * + * @package PhpMyAdmin + */ +class Descriptions +{ + /** + * Return + * Return name or description for a configuration path. + * + * @param string $path Path of configuration + * @param string $type Type of message, either 'name', 'cmt' or 'desc' + * + * @return string + */ + public static function get($path, $type = 'name') + { + $key = str_replace( + [ + 'Servers/1/', + '/', + ], + [ + 'Servers/', + '_', + ], + $path + ); + $value = self::getString($key, $type); + + /* Fallback to path for name and empty string for description and comment */ + if ($value === null) { + if ($type == 'name') { + $value = $path; + } else { + $value = ''; + } + } + + return Sanitize::sanitizeMessage($value); + } + + /** + * Return name or description for a cleaned up configuration path. + * + * @param string $path Path of configuration + * @param string $type Type of message, either 'name', 'cmt' or 'desc' + * + * @return string|null Null if not found + */ + public static function getString($path, $type = 'name') + { + $descriptions = [ + 'AllowArbitraryServer_desc' => __('If enabled, user can enter any MySQL server in login form for cookie auth.'), + 'AllowArbitraryServer_name' => __('Allow login to any MySQL server'), + 'ArbitraryServerRegexp_desc' => __( + 'Restricts the MySQL servers the user can enter when a login to an arbitrary ' + . 'MySQL server is enabled by matching the IP or hostname of the MySQL server ' . + 'to the given regular expression.' + ), + 'ArbitraryServerRegexp_name' => __('Restrict login to MySQL server'), + 'AllowThirdPartyFraming_desc' => __( + 'Enabling this allows a page located on a different domain to call phpMyAdmin ' + . 'inside a frame, and is a potential [strong]security hole[/strong] allowing ' + . 'cross-frame scripting (XSS) attacks.' + ), + 'AllowThirdPartyFraming_name' => __('Allow third party framing'), + 'AllowUserDropDatabase_name' => __('Show "Drop database" link to normal users'), + 'blowfish_secret_desc' => __( + 'Secret passphrase used for encrypting cookies in [kbd]cookie[/kbd] ' + . 'authentication.' + ), + 'blowfish_secret_name' => __('Blowfish secret'), + 'BrowseMarkerEnable_desc' => __('Highlight selected rows.'), + 'BrowseMarkerEnable_name' => __('Row marker'), + 'BrowsePointerEnable_desc' => __('Highlight row pointed by the mouse cursor.'), + 'BrowsePointerEnable_name' => __('Highlight pointer'), + 'BZipDump_desc' => __( + 'Enable bzip2 compression for' + . ' import operations.' + ), + 'BZipDump_name' => __('Bzip2'), + 'CharEditing_desc' => __( + 'Defines which type of editing controls should be used for CHAR and VARCHAR ' + . 'columns; [kbd]input[/kbd] - allows limiting of input length, ' + . '[kbd]textarea[/kbd] - allows newlines in columns.' + ), + 'CharEditing_name' => __('CHAR columns editing'), + 'CodemirrorEnable_desc' => __( + 'Use user-friendly editor for editing SQL queries ' + . '(CodeMirror) with syntax highlighting and ' + . 'line numbers.' + ), + 'CodemirrorEnable_name' => __('Enable CodeMirror'), + 'LintEnable_desc' => __( + 'Find any errors in the query before executing it.' + . ' Requires CodeMirror to be enabled.' + ), + 'LintEnable_name' => __('Enable linter'), + 'MinSizeForInputField_desc' => __( + 'Defines the minimum size for input fields generated for CHAR and VARCHAR ' + . 'columns.' + ), + 'MinSizeForInputField_name' => __('Minimum size for input field'), + 'MaxSizeForInputField_desc' => __( + 'Defines the maximum size for input fields generated for CHAR and VARCHAR ' + . 'columns.' + ), + 'MaxSizeForInputField_name' => __('Maximum size for input field'), + 'CharTextareaCols_desc' => __('Number of columns for CHAR/VARCHAR textareas.'), + 'CharTextareaCols_name' => __('CHAR textarea columns'), + 'CharTextareaRows_desc' => __('Number of rows for CHAR/VARCHAR textareas.'), + 'CharTextareaRows_name' => __('CHAR textarea rows'), + 'CheckConfigurationPermissions_name' => __('Check config file permissions'), + 'CompressOnFly_desc' => __( + 'Compress gzip exports on the fly without the need for much memory; if ' + . 'you encounter problems with created gzip files disable this feature.' + ), + 'CompressOnFly_name' => __('Compress on the fly'), + 'Confirm_desc' => __( + 'Whether a warning ("Are your really sureā¦") should be displayed ' + . 'when you\'re about to lose data.' + ), + 'Confirm_name' => __('Confirm DROP queries'), + 'DBG_sql_desc' => __('Log SQL queries and their execution time, to be displayed in the console'), + 'DBG_sql_name' => __('Debug SQL'), + 'DefaultTabDatabase_desc' => __('Tab that is displayed when entering a database.'), + 'DefaultTabDatabase_name' => __('Default database tab'), + 'DefaultTabServer_desc' => __('Tab that is displayed when entering a server.'), + 'DefaultTabServer_name' => __('Default server tab'), + 'DefaultTabTable_desc' => __('Tab that is displayed when entering a table.'), + 'DefaultTabTable_name' => __('Default table tab'), + 'EnableAutocompleteForTablesAndColumns_desc' => __('Autocomplete of the table and column names in the SQL queries.'), + 'EnableAutocompleteForTablesAndColumns_name' => __('Enable autocomplete for table and column names'), + 'HideStructureActions_desc' => __('Whether the table structure actions should be hidden.'), + 'ShowColumnComments_name' => __('Show column comments'), + 'ShowColumnComments_desc' => __('Whether column comments should be shown in table structure view'), + 'HideStructureActions_name' => __('Hide table structure actions'), + 'DefaultTransformations_Hex_name' => __('Default transformations for Hex'), + 'DefaultTransformations_Hex_desc' => __('Values for options list for default transformations. These will be overwritten if transformation is filled in at table structure page.'), + 'DefaultTransformations_Substring_name' => __('Default transformations for Substring'), + 'DefaultTransformations_Substring_desc' => __('Values for options list for default transformations. These will be overwritten if transformation is filled in at table structure page.'), + 'DefaultTransformations_Bool2Text_name' => __('Default transformations for Bool2Text'), + 'DefaultTransformations_Bool2Text_desc' => __('Values for options list for default transformations. These will be overwritten if transformation is filled in at table structure page.'), + 'DefaultTransformations_External_name' => __('Default transformations for External'), + 'DefaultTransformations_External_desc' => __('Values for options list for default transformations. These will be overwritten if transformation is filled in at table structure page.'), + 'DefaultTransformations_PreApPend_name' => __('Default transformations for PreApPend'), + 'DefaultTransformations_PreApPend_desc' => __('Values for options list for default transformations. These will be overwritten if transformation is filled in at table structure page.'), + 'DefaultTransformations_DateFormat_name' => __('Default transformations for DateFormat'), + 'DefaultTransformations_DateFormat_desc' => __('Values for options list for default transformations. These will be overwritten if transformation is filled in at table structure page.'), + 'DefaultTransformations_Inline_name' => __('Default transformations for Inline'), + 'DefaultTransformations_Inline_desc' => __('Values for options list for default transformations. These will be overwritten if transformation is filled in at table structure page.'), + 'DefaultTransformations_TextImageLink_name' => __('Default transformations for TextImageLink'), + 'DefaultTransformations_TextImageLink_desc' => __('Values for options list for default transformations. These will be overwritten if transformation is filled in at table structure page.'), + 'DefaultTransformations_TextLink_name' => __('Default transformations for TextLink'), + 'DefaultTransformations_TextLink_desc' => __('Values for options list for default transformations. These will be overwritten if transformation is filled in at table structure page.'), + + 'DisplayServersList_desc' => __('Show server listing as a list instead of a drop down.'), + 'DisplayServersList_name' => __('Display servers as a list'), + 'DisableMultiTableMaintenance_desc' => __( + 'Disable the table maintenance mass operations, like optimizing or repairing ' + . 'the selected tables of a database.' + ), + 'DisableMultiTableMaintenance_name' => __('Disable multi table maintenance'), + 'ExecTimeLimit_desc' => __( + 'Set the number of seconds a script is allowed to run ([kbd]0[/kbd] for no ' + . 'limit).' + ), + 'ExecTimeLimit_name' => __('Maximum execution time'), + 'Export_lock_tables_name' => sprintf( + __('Use %s statement'), + htmlspecialchars('<code>LOCK TABLES</code>') + ), + 'Export_asfile_name' => __('Save as file'), + 'Export_charset_name' => __('Character set of the file'), + 'Export_codegen_format_name' => __('Format'), + 'Export_compression_name' => __('Compression'), + 'Export_csv_columns_name' => __('Put columns names in the first row'), + 'Export_csv_enclosed_name' => __('Columns enclosed with'), + 'Export_csv_escaped_name' => __('Columns escaped with'), + 'Export_csv_null_name' => __('Replace NULL with'), + 'Export_csv_removeCRLF_name' => __('Remove CRLF characters within columns'), + 'Export_csv_separator_name' => __('Columns terminated with'), + 'Export_csv_terminated_name' => __('Lines terminated with'), + 'Export_excel_columns_name' => __('Put columns names in the first row'), + 'Export_excel_edition_name' => __('Excel edition'), + 'Export_excel_null_name' => __('Replace NULL with'), + 'Export_excel_removeCRLF_name' => __('Remove CRLF characters within columns'), + 'Export_file_template_database_name' => __('Database name template'), + 'Export_file_template_server_name' => __('Server name template'), + 'Export_file_template_table_name' => __('Table name template'), + 'Export_format_name' => __('Format'), + 'Export_htmlword_columns_name' => __('Put columns names in the first row'), + 'Export_htmlword_null_name' => __('Replace NULL with'), + 'Export_htmlword_structure_or_data_name' => __('Dump table'), + 'Export_latex_caption_name' => __('Include table caption'), + 'Export_latex_columns_name' => __('Put columns names in the first row'), + 'Export_latex_comments_name' => __('Comments'), + 'Export_latex_data_caption_name' => __('Table caption'), + 'Export_latex_data_continued_caption_name' => __('Continued table caption'), + 'Export_latex_data_label_name' => __('Label key'), + 'Export_latex_mime_name' => __('Media (MIME) type'), + 'Export_latex_null_name' => __('Replace NULL with'), + 'Export_latex_relation_name' => __('Relationships'), + 'Export_latex_structure_caption_name' => __('Table caption'), + 'Export_latex_structure_continued_caption_name' => __('Continued table caption'), + 'Export_latex_structure_label_name' => __('Label key'), + 'Export_latex_structure_or_data_name' => __('Dump table'), + 'Export_method_name' => __('Export method'), + 'Export_ods_columns_name' => __('Put columns names in the first row'), + 'Export_ods_null_name' => __('Replace NULL with'), + 'Export_odt_columns_name' => __('Put columns names in the first row'), + 'Export_odt_comments_name' => __('Comments'), + 'Export_odt_mime_name' => __('Media (MIME) type'), + 'Export_odt_null_name' => __('Replace NULL with'), + 'Export_odt_relation_name' => __('Relationships'), + 'Export_odt_structure_or_data_name' => __('Dump table'), + 'Export_onserver_name' => __('Save on server'), + 'Export_onserver_overwrite_name' => __('Overwrite existing file(s)'), + 'Export_as_separate_files_name' => __('Export as separate files'), + 'Export_quick_export_onserver_name' => __('Save on server'), + 'Export_quick_export_onserver_overwrite_name' => __('Overwrite existing file(s)'), + 'Export_remember_file_template_name' => __('Remember file name template'), + 'Export_sql_auto_increment_name' => __('Add AUTO_INCREMENT value'), + 'Export_sql_backquotes_name' => __('Enclose table and column names with backquotes'), + 'Export_sql_compatibility_name' => __('SQL compatibility mode'), + 'Export_sql_dates_name' => __('Creation/Update/Check dates'), + 'Export_sql_delayed_name' => __('Use delayed inserts'), + 'Export_sql_disable_fk_name' => __('Disable foreign key checks'), + 'Export_sql_views_as_tables_name' => __('Export views as tables'), + 'Export_sql_metadata_name' => __('Export related metadata from phpMyAdmin configuration storage'), + 'Export_sql_create_database_name' => sprintf(__('Add %s'), 'CREATE DATABASE / USE'), + 'Export_sql_drop_database_name' => sprintf(__('Add %s'), 'DROP DATABASE'), + 'Export_sql_drop_table_name' => sprintf( + __('Add %s'), + 'DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT / TRIGGER' + ), + 'Export_sql_create_table_name' => sprintf(__('Add %s'), 'CREATE TABLE'), + 'Export_sql_create_view_name' => sprintf(__('Add %s'), 'CREATE VIEW'), + 'Export_sql_create_trigger_name' => sprintf(__('Add %s'), 'CREATE TRIGGER'), + 'Export_sql_hex_for_binary_name' => __('Use hexadecimal for BINARY & BLOB'), + 'Export_sql_if_not_exists_name' => __( + 'Add IF NOT EXISTS (less efficient as indexes will be generated during' + . ' table creation)' + ), + 'Export_sql_view_current_user' => __('Exclude definition of current user'), + 'Export_sql_or_replace_view_name' => sprintf(__('%s view'), 'OR REPLACE'), + 'Export_sql_ignore_name' => __('Use ignore inserts'), + 'Export_sql_include_comments_name' => __('Comments'), + 'Export_sql_insert_syntax_name' => __('Syntax to use when inserting data'), + 'Export_sql_max_query_size_name' => __('Maximal length of created query'), + 'Export_sql_mime_name' => __('Media (MIME) type'), + 'Export_sql_procedure_function_name' => sprintf(__('Add %s'), 'CREATE PROCEDURE / FUNCTION / EVENT'), + 'Export_sql_relation_name' => __('Relationships'), + 'Export_sql_structure_or_data_name' => __('Dump table'), + 'Export_sql_type_name' => __('Export type'), + 'Export_sql_use_transaction_name' => __('Enclose export in a transaction'), + 'Export_sql_utc_time_name' => __('Export time in UTC'), + 'Export_texytext_columns_name' => __('Put columns names in the first row'), + 'Export_texytext_null_name' => __('Replace NULL with'), + 'Export_texytext_structure_or_data_name' => __('Dump table'), + 'ForeignKeyDropdownOrder_desc' => __( + 'Sort order for items in a foreign-key dropdown box; [kbd]content[/kbd] is ' + . 'the referenced data, [kbd]id[/kbd] is the key value.' + ), + 'ForeignKeyDropdownOrder_name' => __('Foreign key dropdown order'), + 'ForeignKeyMaxLimit_desc' => __('A dropdown will be used if fewer items are present.'), + 'ForeignKeyMaxLimit_name' => __('Foreign key limit'), + 'DefaultForeignKeyChecks_desc' => __('Default value for foreign key checks checkbox for some queries.'), + 'DefaultForeignKeyChecks_name' => __('Foreign key checks'), + 'Form_Browse_name' => __('Browse mode'), + 'Form_Browse_desc' => __('Customize browse mode.'), + 'Form_CodeGen_name' => 'CodeGen', + 'Form_CodeGen_desc' => __('Customize default options.'), + 'Form_Csv_name' => __('CSV'), + 'Form_Csv_desc' => __('Customize default options.'), + 'Form_Developer_name' => __('Developer'), + 'Form_Developer_desc' => __('Settings for phpMyAdmin developers.'), + 'Form_Edit_name' => __('Edit mode'), + 'Form_Edit_desc' => __('Customize edit mode.'), + 'Form_Export_defaults_name' => __('Export defaults'), + 'Form_Export_defaults_desc' => __('Customize default export options.'), + 'Form_General_name' => __('General'), + 'Form_General_desc' => __('Set some commonly used options.'), + 'Form_Import_defaults_name' => __('Import defaults'), + 'Form_Import_defaults_desc' => __('Customize default common import options.'), + 'Form_Import_export_name' => __('Import / export'), + 'Form_Import_export_desc' => __('Set import and export directories and compression options.'), + 'Form_Latex_name' => __('LaTeX'), + 'Form_Latex_desc' => __('Customize default options.'), + 'Form_Navi_databases_name' => __('Databases'), + 'Form_Navi_databases_desc' => __('Databases display options.'), + 'Form_Navi_panel_name' => __('Navigation panel'), + 'Form_Navi_panel_desc' => __('Customize appearance of the navigation panel.'), + 'Form_Navi_tree_name' => __('Navigation tree'), + 'Form_Navi_tree_desc' => __('Customize the navigation tree.'), + 'Form_Navi_servers_name' => __('Servers'), + 'Form_Navi_servers_desc' => __('Servers display options.'), + 'Form_Navi_tables_name' => __('Tables'), + 'Form_Navi_tables_desc' => __('Tables display options.'), + 'Form_Main_panel_name' => __('Main panel'), + 'Form_Microsoft_Office_name' => __('Microsoft Office'), + 'Form_Microsoft_Office_desc' => __('Customize default options.'), + 'Form_Open_Document_name' => 'OpenDocument', + 'Form_Open_Document_desc' => __('Customize default options.'), + 'Form_Other_core_settings_name' => __('Other core settings'), + 'Form_Other_core_settings_desc' => __('Settings that didn\'t fit anywhere else.'), + 'Form_Page_titles_name' => __('Page titles'), + 'Form_Page_titles_desc' => __( + 'Specify browser\'s title bar text. Refer to ' + . '[doc@faq6-27]documentation[/doc] for magic strings that can be used ' + . 'to get special values.' + ), + 'Form_Security_name' => __('Security'), + 'Form_Security_desc' => __( + 'Please note that phpMyAdmin is just a user interface and its features do not ' + . 'limit MySQL.' + ), + 'Form_Server_name' => __('Basic settings'), + 'Form_Server_auth_name' => __('Authentication'), + 'Form_Server_auth_desc' => __('Authentication settings.'), + 'Form_Server_config_name' => __('Server configuration'), + 'Form_Server_config_desc' => __( + 'Advanced server configuration, do not change these options unless you know ' + . 'what they are for.' + ), + 'Form_Server_desc' => __('Enter server connection parameters.'), + 'Form_Server_pmadb_name' => __('Configuration storage'), + 'Form_Server_pmadb_desc' => __( + 'Configure phpMyAdmin configuration storage to gain access to additional ' + . 'features, see [doc@linked-tables]phpMyAdmin configuration storage[/doc] in ' + . 'documentation.' + ), + 'Form_Server_tracking_name' => __('Changes tracking'), + 'Form_Server_tracking_desc' => __( + 'Tracking of changes made in database. Requires the phpMyAdmin configuration ' + . 'storage.' + ), + 'Form_Sql_name' => __('SQL'), + 'Form_Sql_box_name' => __('SQL Query box'), + 'Form_Sql_box_desc' => __('Customize links shown in SQL Query boxes.'), + 'Form_Sql_desc' => __('Customize default options.'), + 'Form_Sql_queries_name' => __('SQL queries'), + 'Form_Sql_queries_desc' => __('SQL queries settings.'), + 'Form_Startup_name' => __('Startup'), + 'Form_Startup_desc' => __('Customize startup page.'), + 'Form_DbStructure_name' => __('Database structure'), + 'Form_DbStructure_desc' => __('Choose which details to show in the database structure (list of tables).'), + 'Form_TableStructure_name' => __('Table structure'), + 'Form_TableStructure_desc' => __('Settings for the table structure (list of columns).'), + 'Form_Tabs_name' => __('Tabs'), + 'Form_Tabs_desc' => __('Choose how you want tabs to work.'), + 'Form_DisplayRelationalSchema_name' => __('Display relational schema'), + 'Form_DisplayRelationalSchema_desc' => '', + 'PDFDefaultPageSize_name' => __('Paper size'), + 'PDFDefaultPageSize_desc' => '', + 'Form_Databases_name' => __('Databases'), + 'Form_Text_fields_name' => __('Text fields'), + 'Form_Text_fields_desc' => __('Customize text input fields.'), + 'Form_Texy_name' => __('Texy! text'), + 'Form_Texy_desc' => __('Customize default options'), + 'Form_Warnings_name' => __('Warnings'), + 'Form_Warnings_desc' => __('Disable some of the warnings shown by phpMyAdmin.'), + 'Form_Console_name' => __('Console'), + 'GZipDump_desc' => __( + 'Enable gzip compression for import ' + . 'and export operations.' + ), + 'GZipDump_name' => __('GZip'), + 'IconvExtraParams_name' => __('Extra parameters for iconv'), + 'IgnoreMultiSubmitErrors_desc' => __( + 'If enabled, phpMyAdmin continues computing multiple-statement queries even if ' + . 'one of the queries failed.' + ), + 'IgnoreMultiSubmitErrors_name' => __('Ignore multiple statement errors'), + 'Import_allow_interrupt_desc' => __( + 'Allow interrupt of import in case script detects it is close to time limit. ' + . 'This might be a good way to import large files, however it can break ' + . 'transactions.' + ), + 'enable_drag_drop_import_name' => __('Enable drag and drop import'), + 'enable_drag_drop_import_desc' => __('Uncheck the checkbox to disable drag and drop import'), + 'Import_allow_interrupt_name' => __('Partial import: allow interrupt'), + 'Import_charset_name' => __('Character set of the file'), + 'Import_csv_col_names_name' => __('Lines terminated with'), + 'Import_csv_enclosed_name' => __('Columns enclosed with'), + 'Import_csv_escaped_name' => __('Columns escaped with'), + 'Import_csv_ignore_name' => __('Do not abort on INSERT error'), + 'Import_csv_replace_name' => __('Add ON DUPLICATE KEY UPDATE'), + 'Import_csv_replace_desc' => __('Update data when duplicate keys found on import'), + 'Import_csv_terminated_name' => __('Columns terminated with'), + 'Import_format_desc' => __( + 'Default format; be aware that this list depends on location (database, table) ' + . 'and only SQL is always available.' + ), + 'Import_format_name' => __('Format of imported file'), + 'Import_ldi_enclosed_name' => __('Columns enclosed with'), + 'Import_ldi_escaped_name' => __('Columns escaped with'), + 'Import_ldi_ignore_name' => __('Do not abort on INSERT error'), + 'Import_ldi_local_option_name' => __('Use LOCAL keyword'), + 'Import_ldi_replace_name' => __('Add ON DUPLICATE KEY UPDATE'), + 'Import_ldi_replace_desc' => __('Update data when duplicate keys found on import'), + 'Import_ldi_terminated_name' => __('Columns terminated with'), + 'Import_ods_col_names_name' => __('Column names in first row'), + 'Import_ods_empty_rows_name' => __('Do not import empty rows'), + 'Import_ods_recognize_currency_name' => __('Import currencies ($5.00 to 5.00)'), + 'Import_ods_recognize_percentages_name' => __('Import percentages as proper decimals (12.00% to .12)'), + 'Import_skip_queries_desc' => __('Number of queries to skip from start.'), + 'Import_skip_queries_name' => __('Partial import: skip queries'), + 'Import_sql_compatibility_name' => __('SQL compatibility mode'), + 'Import_sql_no_auto_value_on_zero_name' => __('Do not use AUTO_INCREMENT for zero values'), + 'Import_sql_read_as_multibytes_name' => __('Read as multibytes'), + 'InitialSlidersState_name' => __('Initial state for sliders'), + 'InsertRows_desc' => __('How many rows can be inserted at one time.'), + 'InsertRows_name' => __('Number of inserted rows'), + 'LimitChars_desc' => __('Maximum number of characters shown in any non-numeric column on browse view.'), + 'LimitChars_name' => __('Limit column characters'), + 'LoginCookieDeleteAll_desc' => __( + 'If TRUE, logout deletes cookies for all servers; when set to FALSE, logout ' + . 'only occurs for the current server. Setting this to FALSE makes it easy to ' + . 'forget to log out from other servers when connected to multiple servers.' + ), + 'LoginCookieDeleteAll_name' => __('Delete all cookies on logout'), + 'LoginCookieRecall_desc' => __( + 'Define whether the previous login should be recalled or not in ' + . '[kbd]cookie[/kbd] authentication mode.' + ), + 'LoginCookieRecall_name' => __('Recall user name'), + 'LoginCookieStore_desc' => __( + 'Defines how long (in seconds) a login cookie should be stored in browser. ' + . 'The default of 0 means that it will be kept for the existing session only, ' + . 'and will be deleted as soon as you close the browser window. This is ' + . 'recommended for non-trusted environments.' + ), + 'LoginCookieStore_name' => __('Login cookie store'), + 'LoginCookieValidity_desc' => __('Define how long (in seconds) a login cookie is valid.'), + 'LoginCookieValidity_name' => __('Login cookie validity'), + 'LongtextDoubleTextarea_desc' => __('Double size of textarea for LONGTEXT columns.'), + 'LongtextDoubleTextarea_name' => __('Bigger textarea for LONGTEXT'), + 'MaxCharactersInDisplayedSQL_desc' => __('Maximum number of characters used when a SQL query is displayed.'), + 'MaxCharactersInDisplayedSQL_name' => __('Maximum displayed SQL length'), + 'MaxDbList_cmt' => __('Users cannot set a higher value'), + 'MaxDbList_desc' => __('Maximum number of databases displayed in database list.'), + 'MaxDbList_name' => __('Maximum databases'), + 'FirstLevelNavigationItems_desc' => __( + 'The number of items that can be displayed on each page on the first level' + . ' of the navigation tree.' + ), + 'FirstLevelNavigationItems_name' => __('Maximum items on first level'), + 'MaxNavigationItems_desc' => __('The number of items that can be displayed on each page of the navigation tree.'), + 'MaxNavigationItems_name' => __('Maximum items in branch'), + 'MaxRows_desc' => __( + 'Number of rows displayed when browsing a result set. If the result set ' + . 'contains more rows, "Previous" and "Next" links will be ' + . 'shown.' + ), + 'MaxRows_name' => __('Maximum number of rows to display'), + 'MaxTableList_cmt' => __('Users cannot set a higher value'), + 'MaxTableList_desc' => __('Maximum number of tables displayed in table list.'), + 'MaxTableList_name' => __('Maximum tables'), + 'MemoryLimit_desc' => __( + 'The number of bytes a script is allowed to allocate, eg. [kbd]32M[/kbd] ' + . '([kbd]-1[/kbd] for no limit and [kbd]0[/kbd] for no change).' + ), + 'MemoryLimit_name' => __('Memory limit'), + 'ShowDatabasesNavigationAsTree_desc' => __('In the navigation panel, replaces the database tree with a selector'), + 'ShowDatabasesNavigationAsTree_name' => __('Show databases navigation as tree'), + 'NavigationWidth_name' => __('Navigation panel width'), + 'NavigationWidth_desc' => __('Set to 0 to collapse navigation panel.'), + 'NavigationLinkWithMainPanel_desc' => __('Link with main panel by highlighting the current database or table.'), + 'NavigationLinkWithMainPanel_name' => __('Link with main panel'), + 'NavigationDisplayLogo_desc' => __('Show logo in navigation panel.'), + 'NavigationDisplayLogo_name' => __('Display logo'), + 'NavigationLogoLink_desc' => __('URL where logo in the navigation panel will point to.'), + 'NavigationLogoLink_name' => __('Logo link URL'), + 'NavigationLogoLinkWindow_desc' => __( + 'Open the linked page in the main window ([kbd]main[/kbd]) or in a new one ' + . '([kbd]new[/kbd]).' + ), + 'NavigationLogoLinkWindow_name' => __('Logo link target'), + 'NavigationDisplayServers_desc' => __('Display server choice at the top of the navigation panel.'), + 'NavigationDisplayServers_name' => __('Display servers selection'), + 'NavigationTreeDefaultTabTable_name' => __('Target for quick access icon'), + 'NavigationTreeDefaultTabTable2_name' => __('Target for second quick access icon'), + 'NavigationTreeDisplayItemFilterMinimum_desc' => __( + 'Defines the minimum number of items (tables, views, routines and events) to ' + . 'display a filter box.' + ), + 'NavigationTreeDisplayItemFilterMinimum_name' => __('Minimum number of items to display the filter box'), + 'NavigationTreeDisplayDbFilterMinimum_name' => __('Minimum number of databases to display the database filter box'), + 'NavigationTreeEnableGrouping_desc' => __( + 'Group items in the navigation tree (determined by the separator defined in ' . + 'the Databases and Tables tabs above).' + ), + 'NavigationTreeEnableGrouping_name' => __('Group items in the tree'), + 'NavigationTreeDbSeparator_desc' => __('String that separates databases into different tree levels.'), + 'NavigationTreeDbSeparator_name' => __('Database tree separator'), + 'NavigationTreeTableSeparator_desc' => __('String that separates tables into different tree levels.'), + 'NavigationTreeTableSeparator_name' => __('Table tree separator'), + 'NavigationTreeTableLevel_name' => __('Maximum table tree depth'), + 'NavigationTreePointerEnable_desc' => __('Highlight server under the mouse cursor.'), + 'NavigationTreePointerEnable_name' => __('Enable highlighting'), + 'NavigationTreeEnableExpansion_desc' => __('Whether to offer the possibility of tree expansion in the navigation panel.'), + 'NavigationTreeEnableExpansion_name' => __('Enable navigation tree expansion'), + 'NavigationTreeShowTables_name' => __('Show tables in tree'), + 'NavigationTreeShowTables_desc' => __('Whether to show tables under database in the navigation tree'), + 'NavigationTreeShowViews_name' => __('Show views in tree'), + 'NavigationTreeShowViews_desc' => __('Whether to show views under database in the navigation tree'), + 'NavigationTreeShowFunctions_name' => __('Show functions in tree'), + 'NavigationTreeShowFunctions_desc' => __('Whether to show functions under database in the navigation tree'), + 'NavigationTreeShowProcedures_name' => __('Show procedures in tree'), + 'NavigationTreeShowProcedures_desc' => __('Whether to show procedures under database in the navigation tree'), + 'NavigationTreeShowEvents_name' => __('Show events in tree'), + 'NavigationTreeShowEvents_desc' => __('Whether to show events under database in the navigation tree'), + 'NavigationTreeAutoexpandSingleDb_name' => __('Expand single database'), + 'NavigationTreeAutoexpandSingleDb_desc' => __('Whether to expand single database in the navigation tree automatically.'), + 'NumRecentTables_desc' => __('Maximum number of recently used tables; set 0 to disable.'), + 'NumFavoriteTables_desc' => __('Maximum number of favorite tables; set 0 to disable.'), + 'NumRecentTables_name' => __('Recently used tables'), + 'NumFavoriteTables_name' => __('Favorite tables'), + 'RowActionLinks_desc' => __('These are Edit, Copy and Delete links.'), + 'RowActionLinks_name' => __('Where to show the table row links'), + 'RowActionLinksWithoutUnique_desc' => __('Whether to show row links even in the absence of a unique key.'), + 'RowActionLinksWithoutUnique_name' => __('Show row links anyway'), + 'DisableShortcutKeys_name' => __('Disable shortcut keys'), + 'DisableShortcutKeys_desc' => __('Disable shortcut keys'), + 'NaturalOrder_desc' => __('Use natural order for sorting table and database names.'), + 'NaturalOrder_name' => __('Natural order'), + 'TableNavigationLinksMode_desc' => __('Use only icons, only text or both.'), + 'TableNavigationLinksMode_name' => __('Table navigation bar'), + 'OBGzip_desc' => __('Use GZip output buffering for increased speed in HTTP transfers.'), + 'OBGzip_name' => __('GZip output buffering'), + 'Order_desc' => __( + '[kbd]SMART[/kbd] - i.e. descending order for columns of type TIME, DATE, ' + . 'DATETIME and TIMESTAMP, ascending order otherwise.' + ), + 'Order_name' => __('Default sorting order'), + 'PersistentConnections_desc' => __('Use persistent connections to MySQL databases.'), + 'PersistentConnections_name' => __('Persistent connections'), + 'PmaNoRelation_DisableWarning_desc' => __( + 'Disable the default warning that is displayed on the database details ' + . 'Structure page if any of the required tables for the phpMyAdmin ' + . 'configuration storage could not be found.' + ), + 'PmaNoRelation_DisableWarning_name' => __('Missing phpMyAdmin configuration storage tables'), + 'ReservedWordDisableWarning_desc' => __( + 'Disable the default warning that is displayed on the Structure page if column ' + . 'names in a table are reserved MySQL words.' + ), + 'ReservedWordDisableWarning_name' => __('MySQL reserved word warning'), + 'TabsMode_desc' => __('Use only icons, only text or both.'), + 'TabsMode_name' => __('How to display the menu tabs'), + 'ActionLinksMode_desc' => __('Use only icons, only text or both.'), + 'ActionLinksMode_name' => __('How to display various action links'), + 'ProtectBinary_desc' => __('Disallow BLOB and BINARY columns from editing.'), + 'ProtectBinary_name' => __('Protect binary columns'), + 'QueryHistoryDB_desc' => __( + 'Enable if you want DB-based query history (requires phpMyAdmin configuration ' + . 'storage). If disabled, this utilizes JS-routines to display query history ' + . '(lost by window close).' + ), + 'QueryHistoryDB_name' => __('Permanent query history'), + 'QueryHistoryMax_cmt' => __('Users cannot set a higher value'), + 'QueryHistoryMax_desc' => __('How many queries are kept in history.'), + 'QueryHistoryMax_name' => __('Query history length'), + 'RecodingEngine_desc' => __('Select which functions will be used for character set conversion.'), + 'RecodingEngine_name' => __('Recoding engine'), + 'RememberSorting_desc' => __('When browsing tables, the sorting of each table is remembered.'), + 'RememberSorting_name' => __('Remember table\'s sorting'), + 'TablePrimaryKeyOrder_desc' => __('Default sort order for tables with a primary key.'), + 'TablePrimaryKeyOrder_name' => __('Primary key default sort order'), + 'RepeatCells_desc' => __('Repeat the headers every X cells, [kbd]0[/kbd] deactivates this feature.'), + 'RepeatCells_name' => __('Repeat headers'), + 'GridEditing_name' => __('Grid editing: trigger action'), + 'RelationalDisplay_name' => __('Relational display'), + 'RelationalDisplay_desc' => __('For display Options'), + 'SaveCellsAtOnce_name' => __('Grid editing: save all edited cells at once'), + 'SaveDir_desc' => __('Directory where exports can be saved on server.'), + 'SaveDir_name' => __('Save directory'), + 'Servers_AllowDeny_order_desc' => __('Leave blank if not used.'), + 'Servers_AllowDeny_order_name' => __('Host authorization order'), + 'Servers_AllowDeny_rules_desc' => __('Leave blank for defaults.'), + 'Servers_AllowDeny_rules_name' => __('Host authorization rules'), + 'Servers_AllowNoPassword_name' => __('Allow logins without a password'), + 'Servers_AllowRoot_name' => __('Allow root login'), + 'Servers_SessionTimeZone_name' => __('Session timezone'), + 'Servers_SessionTimeZone_desc' => __( + 'Sets the effective timezone; possibly different than the one from your ' + . 'database server' + ), + 'Servers_auth_http_realm_desc' => __('HTTP Basic Auth Realm name to display when doing HTTP Auth.'), + 'Servers_auth_http_realm_name' => __('HTTP Realm'), + 'Servers_auth_type_desc' => __('Authentication method to use.'), + 'Servers_auth_type_name' => __('Authentication type'), + 'Servers_bookmarktable_desc' => __( + 'Leave blank for no [doc@bookmarks@]bookmark[/doc] ' + . 'support, suggested: [kbd]pma__bookmark[/kbd]' + ), + 'Servers_bookmarktable_name' => __('Bookmark table'), + 'Servers_column_info_desc' => __( + 'Leave blank for no column comments/media (MIME) types, suggested: ' + . '[kbd]pma__column_info[/kbd].' + ), + 'Servers_column_info_name' => __('Column information table'), + 'Servers_compress_desc' => __('Compress connection to MySQL server.'), + 'Servers_compress_name' => __('Compress connection'), + 'Servers_controlpass_name' => __('Control user password'), + 'Servers_controluser_desc' => __( + 'A special MySQL user configured with limited permissions, more information ' + . 'available on [doc@linked-tables]documentation[/doc].' + ), + 'Servers_controluser_name' => __('Control user'), + 'Servers_controlhost_desc' => __( + 'An alternate host to hold the configuration storage; leave blank to use the ' + . 'already defined host.' + ), + 'Servers_controlhost_name' => __('Control host'), + 'Servers_controlport_desc' => __( + 'An alternate port to connect to the host that holds the configuration storage; ' + . 'leave blank to use the default port, or the already defined port, if the ' + . 'controlhost equals host.' + ), + 'Servers_controlport_name' => __('Control port'), + 'Servers_hide_db_desc' => __('Hide databases matching regular expression (PCRE).'), + 'Servers_DisableIS_desc' => __( + 'More information on [a@https://github.com/phpmyadmin/phpmyadmin/issues/8970]phpMyAdmin ' + . 'issue tracker[/a] and [a@https://bugs.mysql.com/19588]MySQL Bugs[/a]' + ), + 'Servers_DisableIS_name' => __('Disable use of INFORMATION_SCHEMA'), + 'Servers_hide_db_name' => __('Hide databases'), + 'Servers_history_desc' => __( + 'Leave blank for no SQL query history support, suggested: ' + . '[kbd]pma__history[/kbd].' + ), + 'Servers_history_name' => __('SQL query history table'), + 'Servers_host_desc' => __('Hostname where MySQL server is running.'), + 'Servers_host_name' => __('Server hostname'), + 'Servers_LogoutURL_name' => __('Logout URL'), + 'Servers_MaxTableUiprefs_desc' => __( + 'Limits number of table preferences which are stored in database, the oldest ' + . 'records are automatically removed.' + ), + 'Servers_MaxTableUiprefs_name' => __('Maximal number of table preferences to store'), + 'Servers_savedsearches_name' => __('QBE saved searches table'), + 'Servers_savedsearches_desc' => __( + 'Leave blank for no QBE saved searches support, suggested: ' + . '[kbd]pma__savedsearches[/kbd].' + ), + 'Servers_export_templates_name' => __('Export templates table'), + 'Servers_export_templates_desc' => __( + 'Leave blank for no export template support, suggested: ' + . '[kbd]pma__export_templates[/kbd].' + ), + 'Servers_central_columns_name' => __('Central columns table'), + 'Servers_central_columns_desc' => __( + 'Leave blank for no central columns support, suggested: ' + . '[kbd]pma__central_columns[/kbd].' + ), + 'Servers_only_db_desc' => __( + 'You can use MySQL wildcard characters (% and _), escape them if you want to ' + . 'use their literal instances, i.e. use [kbd]\'my\_db\'[/kbd] and not ' + . '[kbd]\'my_db\'[/kbd].' + ), + 'Servers_only_db_name' => __('Show only listed databases'), + 'Servers_password_desc' => __('Leave empty if not using config auth.'), + 'Servers_password_name' => __('Password for config auth'), + 'Servers_pdf_pages_desc' => __('Leave blank for no PDF schema support, suggested: [kbd]pma__pdf_pages[/kbd].'), + 'Servers_pdf_pages_name' => __('PDF schema: pages table'), + 'Servers_pmadb_desc' => __( + 'Database used for relations, bookmarks, and PDF features. See ' + . '[doc@linked-tables]pmadb[/doc] for complete information. ' + . 'Leave blank for no support. Suggested: [kbd]phpmyadmin[/kbd].' + ), + 'Servers_pmadb_name' => __('Database name'), + 'Servers_port_desc' => __('Port on which MySQL server is listening, leave empty for default.'), + 'Servers_port_name' => __('Server port'), + 'Servers_recent_desc' => __( + 'Leave blank for no "persistent" recently used tables across sessions, ' + . 'suggested: [kbd]pma__recent[/kbd].' + ), + 'Servers_recent_name' => __('Recently used table'), + 'Servers_favorite_desc' => __( + 'Leave blank for no "persistent" favorite tables across sessions, ' + . 'suggested: [kbd]pma__favorite[/kbd].' + ), + 'Servers_favorite_name' => __('Favorites table'), + 'Servers_relation_desc' => __( + 'Leave blank for no ' + . '[doc@relations@]relation-links[/doc] support, ' + . 'suggested: [kbd]pma__relation[/kbd].' + ), + 'Servers_relation_name' => __('Relation table'), + 'Servers_SignonSession_desc' => __( + 'See [doc@authentication-modes]authentication ' + . 'types[/doc] for an example.' + ), + 'Servers_SignonSession_name' => __('Signon session name'), + 'Servers_SignonURL_name' => __('Signon URL'), + 'Servers_socket_desc' => __('Socket on which MySQL server is listening, leave empty for default.'), + 'Servers_socket_name' => __('Server socket'), + 'Servers_ssl_desc' => __('Enable SSL for connection to MySQL server.'), + 'Servers_ssl_name' => __('Use SSL'), + 'Servers_table_coords_desc' => __('Leave blank for no PDF schema support, suggested: [kbd]pma__table_coords[/kbd].'), + 'Servers_table_coords_name' => __('Designer and PDF schema: table coordinates'), + 'Servers_table_info_desc' => __( + 'Table to describe the display columns, leave blank for no support; ' + . 'suggested: [kbd]pma__table_info[/kbd].' + ), + 'Servers_table_info_name' => __('Display columns table'), + 'Servers_table_uiprefs_desc' => __( + 'Leave blank for no "persistent" tables\' UI preferences across sessions, ' + . 'suggested: [kbd]pma__table_uiprefs[/kbd].' + ), + 'Servers_table_uiprefs_name' => __('UI preferences table'), + 'Servers_tracking_add_drop_database_desc' => __( + 'Whether a DROP DATABASE IF EXISTS statement will be added as first line to ' + . 'the log when creating a database.' + ), + 'Servers_tracking_add_drop_database_name' => __('Add DROP DATABASE'), + 'Servers_tracking_add_drop_table_desc' => __( + 'Whether a DROP TABLE IF EXISTS statement will be added as first line to the ' + . 'log when creating a table.' + ), + 'Servers_tracking_add_drop_table_name' => __('Add DROP TABLE'), + 'Servers_tracking_add_drop_view_desc' => __( + 'Whether a DROP VIEW IF EXISTS statement will be added as first line to the ' + . 'log when creating a view.' + ), + 'Servers_tracking_add_drop_view_name' => __('Add DROP VIEW'), + 'Servers_tracking_default_statements_desc' => __('Defines the list of statements the auto-creation uses for new versions.'), + 'Servers_tracking_default_statements_name' => __('Statements to track'), + 'Servers_tracking_desc' => __( + 'Leave blank for no SQL query tracking support, suggested: ' + . '[kbd]pma__tracking[/kbd].' + ), + 'Servers_tracking_name' => __('SQL query tracking table'), + 'Servers_tracking_version_auto_create_desc' => __( + 'Whether the tracking mechanism creates versions for tables and views ' + . 'automatically.' + ), + 'Servers_tracking_version_auto_create_name' => __('Automatically create versions'), + 'Servers_userconfig_desc' => __( + 'Leave blank for no user preferences storage in database, suggested: ' + . '[kbd]pma__userconfig[/kbd].' + ), + 'Servers_userconfig_name' => __('User preferences storage table'), + 'Servers_users_desc' => __( + 'Both this table and the user groups table are required to enable the ' . + 'configurable menus feature; leaving either one of them blank will disable ' . + 'this feature, suggested: [kbd]pma__users[/kbd].' + ), + 'Servers_users_name' => __('Users table'), + 'Servers_usergroups_desc' => __( + 'Both this table and the users table are required to enable the configurable ' . + 'menus feature; leaving either one of them blank will disable this feature, ' . + 'suggested: [kbd]pma__usergroups[/kbd].' + ), + 'Servers_usergroups_name' => __('User groups table'), + 'Servers_navigationhiding_desc' => __( + 'Leave blank to disable the feature to hide and show navigation items, ' . + 'suggested: [kbd]pma__navigationhiding[/kbd].' + ), + 'Servers_navigationhiding_name' => __('Hidden navigation items table'), + 'Servers_user_desc' => __('Leave empty if not using config auth.'), + 'Servers_user_name' => __('User for config auth'), + 'Servers_verbose_desc' => __( + 'A user-friendly description of this server. Leave blank to display the ' . + 'hostname instead.' + ), + 'Servers_verbose_name' => __('Verbose name of this server'), + 'ShowAll_desc' => __('Whether a user should be displayed a "show all (rows)" button.'), + 'ShowAll_name' => __('Allow to display all the rows'), + 'ShowChgPassword_desc' => __( + 'Please note that enabling this has no effect with [kbd]config[/kbd] ' . + 'authentication mode because the password is hard coded in the configuration ' . + 'file; this does not limit the ability to execute the same command directly.' + ), + 'ShowChgPassword_name' => __('Show password change form'), + 'ShowCreateDb_name' => __('Show create database form'), + 'ShowDbStructureComment_desc' => __('Show or hide a column displaying the comments for all tables.'), + 'ShowDbStructureComment_name' => __('Show table comments'), + 'ShowDbStructureCreation_desc' => __('Show or hide a column displaying the Creation timestamp for all tables.'), + 'ShowDbStructureCreation_name' => __('Show creation timestamp'), + 'ShowDbStructureLastUpdate_desc' => __('Show or hide a column displaying the Last update timestamp for all tables.'), + 'ShowDbStructureLastUpdate_name' => __('Show last update timestamp'), + 'ShowDbStructureLastCheck_desc' => __('Show or hide a column displaying the Last check timestamp for all tables.'), + 'ShowDbStructureLastCheck_name' => __('Show last check timestamp'), + 'ShowDbStructureCharset_desc' => __('Show or hide a column displaying the charset for all tables.'), + 'ShowDbStructureCharset_name' => __('Show table charset'), + 'ShowFieldTypesInDataEditView_desc' => __( + 'Defines whether or not type fields should be initially displayed in ' . + 'edit/insert mode.' + ), + 'ShowFieldTypesInDataEditView_name' => __('Show field types'), + 'ShowFunctionFields_desc' => __('Display the function fields in edit/insert mode.'), + 'ShowFunctionFields_name' => __('Show function fields'), + 'ShowHint_desc' => __('Whether to show hint or not.'), + 'ShowHint_name' => __('Show hint'), + 'ShowPhpInfo_desc' => __( + 'Shows link to [a@https://php.net/manual/function.phpinfo.php]phpinfo()[/a] ' . + 'output.' + ), + 'ShowPhpInfo_name' => __('Show phpinfo() link'), + 'ShowServerInfo_name' => __('Show detailed MySQL server information'), + 'ShowSQL_desc' => __('Defines whether SQL queries generated by phpMyAdmin should be displayed.'), + 'ShowSQL_name' => __('Show SQL queries'), + 'RetainQueryBox_desc' => __('Defines whether the query box should stay on-screen after its submission.'), + 'RetainQueryBox_name' => __('Retain query box'), + 'ShowStats_desc' => __('Allow to display database and table statistics (eg. space usage).'), + 'ShowStats_name' => __('Show statistics'), + 'SkipLockedTables_desc' => __('Mark used tables and make it possible to show databases with locked tables.'), + 'SkipLockedTables_name' => __('Skip locked tables'), + 'SQLQuery_Edit_name' => __('Edit'), + 'SQLQuery_Explain_name' => __('Explain SQL'), + 'SQLQuery_Refresh_name' => __('Refresh'), + 'SQLQuery_ShowAsPHP_name' => __('Create PHP code'), + 'SuhosinDisableWarning_desc' => __( + 'Disable the default warning that is displayed on the main page if Suhosin is ' . + 'detected.' + ), + 'SuhosinDisableWarning_name' => __('Suhosin warning'), + 'LoginCookieValidityDisableWarning_desc' => __( + 'Disable the default warning that is displayed on the main page if the value ' . + 'of the PHP setting session.gc_maxlifetime is less than the value of ' . + '`LoginCookieValidity`.' + ), + 'LoginCookieValidityDisableWarning_name' => __('Login cookie validity warning'), + 'TextareaCols_desc' => __( + 'Textarea size (columns) in edit mode, this value will be emphasized for SQL ' . + 'query textareas (*2).' + ), + 'TextareaCols_name' => __('Textarea columns'), + 'TextareaRows_desc' => __( + 'Textarea size (rows) in edit mode, this value will be emphasized for SQL ' . + 'query textareas (*2).' + ), + 'TextareaRows_name' => __('Textarea rows'), + 'TitleDatabase_desc' => __('Title of browser window when a database is selected.'), + 'TitleDatabase_name' => __('Database'), + 'TitleDefault_desc' => __('Title of browser window when nothing is selected.'), + 'TitleDefault_name' => __('Default title'), + 'TitleServer_desc' => __('Title of browser window when a server is selected.'), + 'TitleServer_name' => __('Server'), + 'TitleTable_desc' => __('Title of browser window when a table is selected.'), + 'TitleTable_name' => __('Table'), + 'TrustedProxies_desc' => __( + 'Input proxies as [kbd]IP: trusted HTTP header[/kbd]. The following example ' . + 'specifies that phpMyAdmin should trust a HTTP_X_FORWARDED_FOR ' . + '(X-Forwarded-For) header coming from the proxy 1.2.3.4:[br][kbd]1.2.3.4: ' . + 'HTTP_X_FORWARDED_FOR[/kbd].' + ), + 'TrustedProxies_name' => __('List of trusted proxies for IP allow/deny'), + 'UploadDir_desc' => __('Directory on server where you can upload files for import.'), + 'UploadDir_name' => __('Upload directory'), + 'UseDbSearch_desc' => __('Allow for searching inside the entire database.'), + 'UseDbSearch_name' => __('Use database search'), + 'UserprefsDeveloperTab_desc' => __( + 'When disabled, users cannot set any of the options below, regardless of the ' . + 'checkbox on the right.' + ), + 'UserprefsDeveloperTab_name' => __('Enable the Developer tab in settings'), + 'VersionCheck_desc' => __('Enables check for latest version on main phpMyAdmin page.'), + 'VersionCheck_name' => __('Version check'), + 'ProxyUrl_desc' => __( + 'The url of the proxy to be used when retrieving the information about the ' . + 'latest version of phpMyAdmin or when submitting error reports. You need this ' . + 'if the server where phpMyAdmin is installed does not have direct access to ' . + 'the internet. The format is: "hostname:portnumber".' + ), + 'ProxyUrl_name' => __('Proxy url'), + 'ProxyUser_desc' => __( + 'The username for authenticating with the proxy. By default, no ' . + 'authentication is performed. If a username is supplied, Basic ' . + 'Authentication will be performed. No other types of authentication are ' . + 'currently supported.' + ), + 'ProxyUser_name' => __('Proxy username'), + 'ProxyPass_desc' => __('The password for authenticating with the proxy.'), + 'ProxyPass_name' => __('Proxy password'), + + 'ZipDump_desc' => __('Enable ZIP compression for import and export operations.'), + 'ZipDump_name' => __('ZIP'), + 'CaptchaLoginPublicKey_desc' => __('Enter your public key for your domain reCaptcha service.'), + 'CaptchaLoginPublicKey_name' => __('Public key for reCaptcha'), + 'CaptchaLoginPrivateKey_desc' => __('Enter your private key for your domain reCaptcha service.'), + 'CaptchaLoginPrivateKey_name' => __('Private key for reCaptcha'), + + 'SendErrorReports_desc' => __('Choose the default action when sending error reports.'), + 'SendErrorReports_name' => __('Send error reports'), + + 'ConsoleEnterExecutes_desc' => __( + 'Queries are executed by pressing Enter (instead of Ctrl+Enter). New lines ' . + 'will be inserted with Shift+Enter.' + ), + 'ConsoleEnterExecutes_name' => __('Enter executes queries in console'), + + 'ZeroConf_desc' => __( + 'Enable Zero Configuration mode which lets you setup phpMyAdmin ' + . 'configuration storage tables automatically.' + ), + 'ZeroConf_name' => __('Enable Zero Configuration mode'), + 'Console_StartHistory_name' => __('Show query history at start'), + 'Console_AlwaysExpand_name' => __('Always expand query messages'), + 'Console_CurrentQuery_name' => __('Show current browsing query'), + 'Console_EnterExecutes_name' => __('Execute queries on Enter and insert new line with Shift + Enter'), + 'Console_DarkTheme_name' => __('Switch to dark theme'), + 'Console_Height_name' => __('Console height'), + 'Console_Mode_name' => __('Console mode'), + 'Console_GroupQueries_name' => __('Group queries'), + 'Console_Order_name' => __('Order'), + 'Console_OrderBy_name' => __('Order by'), + 'DefaultConnectionCollation_name' => __('Server connection collation'), + ]; + + $key = $path . '_' . $type; + + return $descriptions[$key] ?? null; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Form.php b/srcs/phpmyadmin/libraries/classes/Config/Form.php new file mode 100644 index 0000000..8c1cbc5 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Form.php @@ -0,0 +1,238 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Form handling code. + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Config\ConfigFile; + +/** + * Base class for forms, loads default configuration options, checks allowed + * values etc. + * + * @package PhpMyAdmin + */ +class Form +{ + /** + * Form name + * @var string + */ + public $name; + + /** + * Arbitrary index, doesn't affect class' behavior + * @var int + */ + public $index; + + /** + * Form fields (paths), filled by {@link readFormPaths()}, indexed by field name + * @var array + */ + public $fields; + + /** + * Stores default values for some fields (eg. pmadb tables) + * @var array + */ + public $default; + + /** + * Caches field types, indexed by field names + * @var array + */ + private $_fieldsTypes; + + /** + * ConfigFile instance + * @var ConfigFile + */ + private $_configFile; + + /** + * Constructor, reads default config values + * + * @param string $formName Form name + * @param array $form Form data + * @param ConfigFile $cf Config file instance + * @param int $index arbitrary index, stored in Form::$index + */ + public function __construct( + $formName, + array $form, + ConfigFile $cf, + $index = null + ) { + $this->index = $index; + $this->_configFile = $cf; + $this->loadForm($formName, $form); + } + + /** + * Returns type of given option + * + * @param string $optionName path or field name + * + * @return string|null one of: boolean, integer, double, string, select, array + */ + public function getOptionType($optionName) + { + $key = ltrim( + mb_substr( + $optionName, + (int) mb_strrpos($optionName, '/') + ), + '/' + ); + return isset($this->_fieldsTypes[$key]) + ? $this->_fieldsTypes[$key] + : null; + } + + /** + * Returns allowed values for select fields + * + * @param string $optionPath Option path + * + * @return array + */ + public function getOptionValueList($optionPath) + { + $value = $this->_configFile->getDbEntry($optionPath); + if ($value === null) { + trigger_error("$optionPath - select options not defined", E_USER_ERROR); + return []; + } + if (! is_array($value)) { + trigger_error("$optionPath - not a static value list", E_USER_ERROR); + return []; + } + // convert array('#', 'a', 'b') to array('a', 'b') + if (isset($value[0]) && $value[0] === '#') { + // remove first element ('#') + array_shift($value); + // $value has keys and value names, return it + return $value; + } + + // convert value list array('a', 'b') to array('a' => 'a', 'b' => 'b') + $hasStringKeys = false; + $keys = []; + for ($i = 0, $nb = count($value); $i < $nb; $i++) { + if (! isset($value[$i])) { + $hasStringKeys = true; + break; + } + $keys[] = is_bool($value[$i]) ? (int) $value[$i] : $value[$i]; + } + if (! $hasStringKeys) { + $value = array_combine($keys, $value); + } + + // $value has keys and value names, return it + return $value; + } + + /** + * array_walk callback function, reads path of form fields from + * array (see docs for \PhpMyAdmin\Config\Forms\BaseForm::getForms) + * + * @param mixed $value Value + * @param mixed $key Key + * @param mixed $prefix Prefix + * + * @return void + */ + private function _readFormPathsCallback($value, $key, $prefix) + { + static $groupCounter = 0; + + if (is_array($value)) { + $prefix .= $key . '/'; + array_walk($value, [$this, '_readFormPathsCallback'], $prefix); + return; + } + + if (! is_int($key)) { + $this->default[$prefix . $key] = $value; + $value = $key; + } + // add unique id to group ends + if ($value == ':group:end') { + $value .= ':' . $groupCounter++; + } + $this->fields[] = $prefix . $value; + } + + /** + * Reads form paths to {@link $fields} + * + * @param array $form Form + * + * @return void + */ + protected function readFormPaths(array $form) + { + // flatten form fields' paths and save them to $fields + $this->fields = []; + array_walk($form, [$this, '_readFormPathsCallback'], ''); + + // $this->fields is an array of the form: [0..n] => 'field path' + // change numeric indexes to contain field names (last part of the path) + $paths = $this->fields; + $this->fields = []; + foreach ($paths as $path) { + $key = ltrim( + mb_substr($path, (int) mb_strrpos($path, '/')), + '/' + ); + $this->fields[$key] = $path; + } + // now $this->fields is an array of the form: 'field name' => 'field path' + } + + /** + * Reads fields' types to $this->_fieldsTypes + * + * @return void + */ + protected function readTypes() + { + $cf = $this->_configFile; + foreach ($this->fields as $name => $path) { + if (mb_strpos((string) $name, ':group:') === 0) { + $this->_fieldsTypes[$name] = 'group'; + continue; + } + $v = $cf->getDbEntry($path); + if ($v !== null) { + $type = is_array($v) ? 'select' : $v; + } else { + $type = gettype($cf->getDefault($path)); + } + $this->_fieldsTypes[$name] = $type; + } + } + + /** + * Reads form settings and prepares class to work with given subset of + * config file + * + * @param string $formName Form name + * @param array $form Form + * + * @return void + */ + public function loadForm($formName, array $form) + { + $this->name = $formName; + $this->readFormPaths($form); + $this->readTypes(); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/FormDisplay.php b/srcs/phpmyadmin/libraries/classes/Config/FormDisplay.php new file mode 100644 index 0000000..500706c --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/FormDisplay.php @@ -0,0 +1,924 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Form management class, displays and processes forms + * + * Explanation of used terms: + * o work_path - original field path, eg. Servers/4/verbose + * o system_path - work_path modified so that it points to the first server, + * eg. Servers/1/verbose + * o translated_path - work_path modified for HTML field name, a path with + * slashes changed to hyphens, eg. Servers-4-verbose + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Config\Forms\User\UserFormList; +use PhpMyAdmin\Sanitize; +use PhpMyAdmin\Util; + +/** + * Form management class, displays and processes forms + * + * @package PhpMyAdmin + */ +class FormDisplay +{ + /** + * ConfigFile instance + * @var ConfigFile + */ + private $_configFile; + + /** + * Form list + * @var Form[] + */ + private $_forms = []; + + /** + * Stores validation errors, indexed by paths + * [ Form_name ] is an array of form errors + * [path] is a string storing error associated with single field + * @var array + */ + private $_errors = []; + + /** + * Paths changed so that they can be used as HTML ids, indexed by paths + * @var array + */ + private $_translatedPaths = []; + + /** + * Server paths change indexes so we define maps from current server + * path to the first one, indexed by work path + * @var array + */ + private $_systemPaths = []; + + /** + * Language strings which will be sent to Messages JS variable + * Will be looked up in $GLOBALS: str{value} or strSetup{value} + * @var array + */ + private $_jsLangStrings = []; + + /** + * Tells whether forms have been validated + * @var bool + */ + private $_isValidated = true; + + /** + * Dictionary with user preferences keys + * @var array|null + */ + private $_userprefsKeys; + + /** + * Dictionary with disallowed user preferences keys + * @var array + */ + private $_userprefsDisallow; + + /** + * @var FormDisplayTemplate + */ + private $formDisplayTemplate; + + /** + * Constructor + * + * @param ConfigFile $cf Config file instance + */ + public function __construct(ConfigFile $cf) + { + $this->formDisplayTemplate = new FormDisplayTemplate($GLOBALS['PMA_Config']); + $this->_jsLangStrings = [ + 'error_nan_p' => __('Not a positive number!'), + 'error_nan_nneg' => __('Not a non-negative number!'), + 'error_incorrect_port' => __('Not a valid port number!'), + 'error_invalid_value' => __('Incorrect value!'), + 'error_value_lte' => __('Value must be less than or equal to %s!'), + ]; + $this->_configFile = $cf; + // initialize validators + Validator::getValidators($this->_configFile); + } + + /** + * Returns {@link ConfigFile} associated with this instance + * + * @return ConfigFile + */ + public function getConfigFile() + { + return $this->_configFile; + } + + /** + * Registers form in form manager + * + * @param string $formName Form name + * @param array $form Form data + * @param int $serverId 0 if new server, validation; >= 1 if editing a server + * + * @return void + */ + public function registerForm($formName, array $form, $serverId = null) + { + $this->_forms[$formName] = new Form( + $formName, + $form, + $this->_configFile, + $serverId + ); + $this->_isValidated = false; + foreach ($this->_forms[$formName]->fields as $path) { + $workPath = $serverId === null + ? $path + : str_replace('Servers/1/', "Servers/$serverId/", $path); + $this->_systemPaths[$workPath] = $path; + $this->_translatedPaths[$workPath] = str_replace('/', '-', $workPath); + } + } + + /** + * Processes forms, returns true on successful save + * + * @param bool $allowPartialSave allows for partial form saving + * on failed validation + * @param bool $checkFormSubmit whether check for $_POST['submit_save'] + * + * @return boolean whether processing was successful + */ + public function process($allowPartialSave = true, $checkFormSubmit = true) + { + if ($checkFormSubmit && ! isset($_POST['submit_save'])) { + return false; + } + + // save forms + if (count($this->_forms) > 0) { + return $this->save(array_keys($this->_forms), $allowPartialSave); + } + return false; + } + + /** + * Runs validation for all registered forms + * + * @return void + */ + private function _validate() + { + if ($this->_isValidated) { + return; + } + + $paths = []; + $values = []; + foreach ($this->_forms as $form) { + /** @var Form $form */ + $paths[] = $form->name; + // collect values and paths + foreach ($form->fields as $path) { + $workPath = array_search($path, $this->_systemPaths); + $values[$path] = $this->_configFile->getValue($workPath); + $paths[] = $path; + } + } + + // run validation + $errors = Validator::validate( + $this->_configFile, + $paths, + $values, + false + ); + + // change error keys from canonical paths to work paths + if (is_array($errors) && count($errors) > 0) { + $this->_errors = []; + foreach ($errors as $path => $errorList) { + $workPath = array_search($path, $this->_systemPaths); + // field error + if (! $workPath) { + // form error, fix path + $workPath = $path; + } + $this->_errors[$workPath] = $errorList; + } + } + $this->_isValidated = true; + } + + /** + * Outputs HTML for the forms under the menu tab + * + * @param bool $showRestoreDefault whether to show "restore default" + * button besides the input field + * @param array $jsDefault stores JavaScript code + * to be displayed + * @param array $js will be updated with javascript code + * @param bool $showButtons whether show submit and reset button + * + * @return string + */ + private function _displayForms( + $showRestoreDefault, + array &$jsDefault, + array &$js, + $showButtons + ) { + $htmlOutput = ''; + $validators = Validator::getValidators($this->_configFile); + + foreach ($this->_forms as $form) { + /** @var Form $form */ + $formErrors = isset($this->_errors[$form->name]) + ? $this->_errors[$form->name] : null; + $htmlOutput .= $this->formDisplayTemplate->displayFieldsetTop( + Descriptions::get("Form_{$form->name}"), + Descriptions::get("Form_{$form->name}", 'desc'), + $formErrors, + ['id' => $form->name] + ); + + foreach ($form->fields as $field => $path) { + $workPath = array_search($path, $this->_systemPaths); + $translatedPath = $this->_translatedPaths[$workPath]; + // always true/false for user preferences display + // otherwise null + $userPrefsAllow = isset($this->_userprefsKeys[$path]) + ? ! isset($this->_userprefsDisallow[$path]) + : null; + // display input + $htmlOutput .= $this->_displayFieldInput( + $form, + $field, + $path, + $workPath, + $translatedPath, + $showRestoreDefault, + $userPrefsAllow, + $jsDefault + ); + // register JS validators for this field + if (isset($validators[$path])) { + $this->formDisplayTemplate->addJsValidate($translatedPath, $validators[$path], $js); + } + } + $htmlOutput .= $this->formDisplayTemplate->displayFieldsetBottom($showButtons); + } + return $htmlOutput; + } + + /** + * Outputs HTML for forms + * + * @param bool $tabbedForm if true, use a form with tabs + * @param bool $showRestoreDefault whether show "restore default" button + * besides the input field + * @param bool $showButtons whether show submit and reset button + * @param string $formAction action attribute for the form + * @param array|null $hiddenFields array of form hidden fields (key: field + * name) + * + * @return string HTML for forms + */ + public function getDisplay( + $tabbedForm = false, + $showRestoreDefault = false, + $showButtons = true, + $formAction = null, + $hiddenFields = null + ) { + static $jsLangSent = false; + + $htmlOutput = ''; + + $js = []; + $jsDefault = []; + + $htmlOutput .= $this->formDisplayTemplate->displayFormTop($formAction, 'post', $hiddenFields); + + if ($tabbedForm) { + $tabs = []; + foreach ($this->_forms as $form) { + $tabs[$form->name] = Descriptions::get("Form_$form->name"); + } + $htmlOutput .= $this->formDisplayTemplate->displayTabsTop($tabs); + } + + // validate only when we aren't displaying a "new server" form + $isNewServer = false; + foreach ($this->_forms as $form) { + /** @var Form $form */ + if ($form->index === 0) { + $isNewServer = true; + break; + } + } + if (! $isNewServer) { + $this->_validate(); + } + + // user preferences + $this->_loadUserprefsInfo(); + + // display forms + $htmlOutput .= $this->_displayForms( + $showRestoreDefault, + $jsDefault, + $js, + $showButtons + ); + + if ($tabbedForm) { + $htmlOutput .= $this->formDisplayTemplate->displayTabsBottom(); + } + $htmlOutput .= $this->formDisplayTemplate->displayFormBottom(); + + // if not already done, send strings used for validation to JavaScript + if (! $jsLangSent) { + $jsLangSent = true; + $jsLang = []; + foreach ($this->_jsLangStrings as $strName => $strValue) { + $jsLang[] = "'$strName': '" . Sanitize::jsFormat($strValue, false) . '\''; + } + $js[] = "$.extend(Messages, {\n\t" + . implode(",\n\t", $jsLang) . '})'; + } + + $js[] = "$.extend(defaultValues, {\n\t" + . implode(",\n\t", $jsDefault) . '})'; + $htmlOutput .= $this->formDisplayTemplate->displayJavascript($js); + + return $htmlOutput; + } + + /** + * Prepares data for input field display and outputs HTML code + * + * @param Form $form Form object + * @param string $field field name as it appears in $form + * @param string $systemPath field path, eg. Servers/1/verbose + * @param string $workPath work path, eg. Servers/4/verbose + * @param string $translatedPath work path changed so that it can be + * used as XHTML id + * @param bool $showRestoreDefault whether show "restore default" button + * besides the input field + * @param bool|null $userPrefsAllow whether user preferences are enabled + * for this field (null - no support, + * true/false - enabled/disabled) + * @param array $jsDefault array which stores JavaScript code + * to be displayed + * + * @return string|null HTML for input field + */ + private function _displayFieldInput( + Form $form, + $field, + $systemPath, + $workPath, + $translatedPath, + $showRestoreDefault, + $userPrefsAllow, + array &$jsDefault + ) { + $name = Descriptions::get($systemPath); + $description = Descriptions::get($systemPath, 'desc'); + + $value = $this->_configFile->get($workPath); + $valueDefault = $this->_configFile->getDefault($systemPath); + $valueIsDefault = false; + if ($value === null || $value === $valueDefault) { + $value = $valueDefault; + $valueIsDefault = true; + } + + $opts = [ + 'doc' => $this->getDocLink($systemPath), + 'show_restore_default' => $showRestoreDefault, + 'userprefs_allow' => $userPrefsAllow, + 'userprefs_comment' => Descriptions::get($systemPath, 'cmt'), + ]; + if (isset($form->default[$systemPath])) { + $opts['setvalue'] = (string) $form->default[$systemPath]; + } + + if (isset($this->_errors[$workPath])) { + $opts['errors'] = $this->_errors[$workPath]; + } + + $type = ''; + switch ($form->getOptionType($field)) { + case 'string': + $type = 'text'; + break; + case 'short_string': + $type = 'short_text'; + break; + case 'double': + case 'integer': + $type = 'number_text'; + break; + case 'boolean': + $type = 'checkbox'; + break; + case 'select': + $type = 'select'; + $opts['values'] = $form->getOptionValueList($form->fields[$field]); + break; + case 'array': + $type = 'list'; + $value = (array) $value; + $valueDefault = (array) $valueDefault; + break; + case 'group': + // :group:end is changed to :group:end:{unique id} in Form class + $htmlOutput = ''; + if (mb_substr($field, 7, 4) != 'end:') { + $htmlOutput .= $this->formDisplayTemplate->displayGroupHeader( + mb_substr($field, 7) + ); + } else { + $this->formDisplayTemplate->displayGroupFooter(); + } + return $htmlOutput; + case 'NULL': + trigger_error("Field $systemPath has no type", E_USER_WARNING); + return null; + } + + // detect password fields + if ($type === 'text' + && (mb_substr($translatedPath, -9) === '-password' + || mb_substr($translatedPath, -4) === 'pass' + || mb_substr($translatedPath, -4) === 'Pass') + ) { + $type = 'password'; + } + + // TrustedProxies requires changes before displaying + if ($systemPath == 'TrustedProxies') { + foreach ($value as $ip => &$v) { + if (! preg_match('/^-\d+$/', $ip)) { + $v = $ip . ': ' . $v; + } + } + } + $this->_setComments($systemPath, $opts); + + // send default value to form's JS + $jsLine = '\'' . $translatedPath . '\': '; + switch ($type) { + case 'text': + case 'short_text': + case 'number_text': + case 'password': + $jsLine .= '\'' . Sanitize::escapeJsString($valueDefault) . '\''; + break; + case 'checkbox': + $jsLine .= $valueDefault ? 'true' : 'false'; + break; + case 'select': + $valueDefaultJs = is_bool($valueDefault) + ? (int) $valueDefault + : $valueDefault; + $jsLine .= '[\'' . Sanitize::escapeJsString($valueDefaultJs) . '\']'; + break; + case 'list': + $jsLine .= '\'' . Sanitize::escapeJsString(implode("\n", $valueDefault)) + . '\''; + break; + } + $jsDefault[] = $jsLine; + + return $this->formDisplayTemplate->displayInput( + $translatedPath, + $name, + $type, + $value, + $description, + $valueIsDefault, + $opts + ); + } + + /** + * Displays errors + * + * @return string|null HTML for errors + */ + public function displayErrors() + { + $this->_validate(); + if (count($this->_errors) === 0) { + return null; + } + + $htmlOutput = ''; + + foreach ($this->_errors as $systemPath => $errorList) { + if (isset($this->_systemPaths[$systemPath])) { + $name = Descriptions::get($this->_systemPaths[$systemPath]); + } else { + $name = Descriptions::get('Form_' . $systemPath); + } + $htmlOutput .= $this->formDisplayTemplate->displayErrors($name, $errorList); + } + + return $htmlOutput; + } + + /** + * Reverts erroneous fields to their default values + * + * @return void + */ + public function fixErrors() + { + $this->_validate(); + if (count($this->_errors) === 0) { + return; + } + + $cf = $this->_configFile; + foreach (array_keys($this->_errors) as $workPath) { + if (! isset($this->_systemPaths[$workPath])) { + continue; + } + $canonicalPath = $this->_systemPaths[$workPath]; + $cf->set($workPath, $cf->getDefault($canonicalPath)); + } + } + + /** + * Validates select field and casts $value to correct type + * + * @param string $value Current value + * @param array $allowed List of allowed values + * + * @return bool + */ + private function _validateSelect(&$value, array $allowed) + { + $valueCmp = is_bool($value) + ? (int) $value + : $value; + foreach ($allowed as $vk => $v) { + // equality comparison only if both values are numeric or not numeric + // (allows to skip 0 == 'string' equalling to true) + // or identity (for string-string) + if (($vk == $value && ! (is_numeric($valueCmp) xor is_numeric($vk))) + || $vk === $value + ) { + // keep boolean value as boolean + if (! is_bool($value)) { + settype($value, gettype($vk)); + } + return true; + } + } + return false; + } + + /** + * Validates and saves form data to session + * + * @param array|string $forms array of form names + * @param bool $allowPartialSave allows for partial form saving on + * failed validation + * + * @return boolean true on success (no errors and all saved) + */ + public function save($forms, $allowPartialSave = true) + { + $result = true; + $forms = (array) $forms; + + $values = []; + $toSave = []; + $isSetupScript = $GLOBALS['PMA_Config']->get('is_setup'); + if ($isSetupScript) { + $this->_loadUserprefsInfo(); + } + + $this->_errors = []; + foreach ($forms as $formName) { + /** @var Form $form */ + if (isset($this->_forms[$formName])) { + $form = $this->_forms[$formName]; + } else { + continue; + } + // get current server id + $changeIndex = $form->index === 0 + ? $this->_configFile->getServerCount() + 1 + : false; + // grab POST values + foreach ($form->fields as $field => $systemPath) { + $workPath = array_search($systemPath, $this->_systemPaths); + $key = $this->_translatedPaths[$workPath]; + $type = $form->getOptionType($field); + + // skip groups + if ($type == 'group') { + continue; + } + + // ensure the value is set + if (! isset($_POST[$key])) { + // checkboxes aren't set by browsers if they're off + if ($type == 'boolean') { + $_POST[$key] = false; + } else { + $this->_errors[$form->name][] = sprintf( + __('Missing data for %s'), + '<i>' . Descriptions::get($systemPath) . '</i>' + ); + $result = false; + continue; + } + } + + // user preferences allow/disallow + if ($isSetupScript + && isset($this->_userprefsKeys[$systemPath]) + ) { + if (isset($this->_userprefsDisallow[$systemPath]) + && isset($_POST[$key . '-userprefs-allow']) + ) { + unset($this->_userprefsDisallow[$systemPath]); + } elseif (! isset($_POST[$key . '-userprefs-allow'])) { + $this->_userprefsDisallow[$systemPath] = true; + } + } + + // cast variables to correct type + switch ($type) { + case 'double': + $_POST[$key] = Util::requestString($_POST[$key]); + settype($_POST[$key], 'float'); + break; + case 'boolean': + case 'integer': + if ($_POST[$key] !== '') { + $_POST[$key] = Util::requestString($_POST[$key]); + settype($_POST[$key], $type); + } + break; + case 'select': + $successfullyValidated = $this->_validateSelect( + $_POST[$key], + $form->getOptionValueList($systemPath) + ); + if (! $successfullyValidated) { + $this->_errors[$workPath][] = __('Incorrect value!'); + $result = false; + // "continue" for the $form->fields foreach-loop + continue 2; + } + break; + case 'string': + case 'short_string': + $_POST[$key] = Util::requestString($_POST[$key]); + break; + case 'array': + // eliminate empty values and ensure we have an array + $postValues = is_array($_POST[$key]) + ? $_POST[$key] + : explode("\n", $_POST[$key]); + $_POST[$key] = []; + $this->_fillPostArrayParameters($postValues, $key); + break; + } + + // now we have value with proper type + $values[$systemPath] = $_POST[$key]; + if ($changeIndex !== false) { + $workPath = str_replace( + "Servers/$form->index/", + "Servers/$changeIndex/", + $workPath + ); + } + $toSave[$workPath] = $systemPath; + } + } + + // save forms + if (! $allowPartialSave && ! empty($this->_errors)) { + // don't look for non-critical errors + $this->_validate(); + return $result; + } + + foreach ($toSave as $workPath => $path) { + // TrustedProxies requires changes before saving + if ($path == 'TrustedProxies') { + $proxies = []; + $i = 0; + foreach ($values[$path] as $value) { + $matches = []; + $match = preg_match( + "/^(.+):(?:[ ]?)(\\w+)$/", + $value, + $matches + ); + if ($match) { + // correct 'IP: HTTP header' pair + $ip = trim($matches[1]); + $proxies[$ip] = trim($matches[2]); + } else { + // save also incorrect values + $proxies["-$i"] = $value; + $i++; + } + } + $values[$path] = $proxies; + } + $this->_configFile->set($workPath, $values[$path], $path); + } + if ($isSetupScript) { + $this->_configFile->set( + 'UserprefsDisallow', + array_keys($this->_userprefsDisallow) + ); + } + + // don't look for non-critical errors + $this->_validate(); + + return $result; + } + + /** + * Tells whether form validation failed + * + * @return boolean + */ + public function hasErrors() + { + return count($this->_errors) > 0; + } + + + /** + * Returns link to documentation + * + * @param string $path Path to documentation + * + * @return string + */ + public function getDocLink($path) + { + $test = mb_substr($path, 0, 6); + if ($test == 'Import' || $test == 'Export') { + return ''; + } + return Util::getDocuLink( + 'config', + 'cfg_' . $this->_getOptName($path) + ); + } + + /** + * Changes path so it can be used in URLs + * + * @param string $path Path + * + * @return string + */ + private function _getOptName($path) + { + return str_replace(['Servers/1/', '/'], ['Servers/', '_'], $path); + } + + /** + * Fills out {@link userprefs_keys} and {@link userprefs_disallow} + * + * @return void + */ + private function _loadUserprefsInfo() + { + if ($this->_userprefsKeys !== null) { + return; + } + + $this->_userprefsKeys = array_flip(UserFormList::getFields()); + // read real config for user preferences display + $userPrefsDisallow = $GLOBALS['PMA_Config']->get('is_setup') + ? $this->_configFile->get('UserprefsDisallow', []) + : $GLOBALS['cfg']['UserprefsDisallow']; + $this->_userprefsDisallow = array_flip($userPrefsDisallow); + } + + /** + * Sets field comments and warnings based on current environment + * + * @param string $systemPath Path to settings + * @param array $opts Chosen options + * + * @return void + */ + private function _setComments($systemPath, array &$opts) + { + // RecodingEngine - mark unavailable types + if ($systemPath == 'RecodingEngine') { + $comment = ''; + if (! function_exists('iconv')) { + $opts['values']['iconv'] .= ' (' . __('unavailable') . ')'; + $comment = sprintf( + __('"%s" requires %s extension'), + 'iconv', + 'iconv' + ); + } + if (! function_exists('recode_string')) { + $opts['values']['recode'] .= ' (' . __('unavailable') . ')'; + $comment .= ($comment ? ", " : '') . sprintf( + __('"%s" requires %s extension'), + 'recode', + 'recode' + ); + } + /* mbstring is always there thanks to polyfill */ + $opts['comment'] = $comment; + $opts['comment_warning'] = true; + } + // ZipDump, GZipDump, BZipDump - check function availability + if ($systemPath == 'ZipDump' + || $systemPath == 'GZipDump' + || $systemPath == 'BZipDump' + ) { + $comment = ''; + $funcs = [ + 'ZipDump' => [ + 'zip_open', + 'gzcompress', + ], + 'GZipDump' => [ + 'gzopen', + 'gzencode', + ], + 'BZipDump' => [ + 'bzopen', + 'bzcompress', + ], + ]; + if (! function_exists($funcs[$systemPath][0])) { + $comment = sprintf( + __( + 'Compressed import will not work due to missing function %s.' + ), + $funcs[$systemPath][0] + ); + } + if (! function_exists($funcs[$systemPath][1])) { + $comment .= ($comment ? '; ' : '') . sprintf( + __( + 'Compressed export will not work due to missing function %s.' + ), + $funcs[$systemPath][1] + ); + } + $opts['comment'] = $comment; + $opts['comment_warning'] = true; + } + if (! $GLOBALS['PMA_Config']->get('is_setup')) { + if ($systemPath == 'MaxDbList' || $systemPath == 'MaxTableList' + || $systemPath == 'QueryHistoryMax' + ) { + $opts['comment'] = sprintf( + __('maximum %s'), + $GLOBALS['cfg'][$systemPath] + ); + } + } + } + + /** + * Copy items of an array to $_POST variable + * + * @param array $postValues List of parameters + * @param string $key Array key + * + * @return void + */ + private function _fillPostArrayParameters(array $postValues, $key) + { + foreach ($postValues as $v) { + $v = Util::requestString($v); + if ($v !== '') { + $_POST[$key][] = $v; + } + } + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/FormDisplayTemplate.php b/srcs/phpmyadmin/libraries/classes/Config/FormDisplayTemplate.php new file mode 100644 index 0000000..07663b8 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/FormDisplayTemplate.php @@ -0,0 +1,526 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Form templates + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Config; +use PhpMyAdmin\Sanitize; +use PhpMyAdmin\Template; +use PhpMyAdmin\Url; +use PhpMyAdmin\Util; + +/** + * PhpMyAdmin\Config\FormDisplayTemplate class + * + * @package PhpMyAdmin + */ +class FormDisplayTemplate +{ + /** + * @var int + */ + public $group; + + /** + * @var Config + */ + protected $config; + + /** + * @var Template + */ + public $template; + + /** + * FormDisplayTemplate constructor. + * + * @param Config $config Config instance + */ + public function __construct(Config $config) + { + $this->config = $config; + $this->template = new Template(); + } + + /** + * Displays top part of the form + * + * @param string $action default: $_SERVER['REQUEST_URI'] + * @param string $method 'post' or 'get' + * @param array|null $hiddenFields array of form hidden fields (key: field name) + * + * @return string + */ + public function displayFormTop( + $action = null, + $method = 'post', + $hiddenFields = null + ): string { + static $hasCheckPageRefresh = false; + + if ($action === null) { + $action = $_SERVER['REQUEST_URI']; + } + if ($method != 'post') { + $method = 'get'; + } + $htmlOutput = '<form method="' . $method . '" action="' + . htmlspecialchars($action) . '" class="config-form disableAjax">'; + $htmlOutput .= '<input type="hidden" name="tab_hash" value="">'; + // we do validation on page refresh when browser remembers field values, + // add a field with known value which will be used for checks + if (! $hasCheckPageRefresh) { + $hasCheckPageRefresh = true; + $htmlOutput .= '<input type="hidden" name="check_page_refresh" ' + . ' id="check_page_refresh" value="">' . "\n"; + } + $htmlOutput .= Url::getHiddenInputs('', '', 0, 'server') . "\n"; + $htmlOutput .= Url::getHiddenFields((array) $hiddenFields, '', true); + return $htmlOutput; + } + + /** + * Displays form tabs which are given by an array indexed by fieldset id + * ({@link self::displayFieldsetTop}), with values being tab titles. + * + * @param array $tabs tab names + * + * @return string + */ + public function displayTabsTop(array $tabs): string + { + $items = []; + foreach ($tabs as $tabId => $tabName) { + $items[] = [ + 'content' => htmlspecialchars($tabName), + 'url' => [ + 'href' => '#' . $tabId, + ], + ]; + } + + $htmlOutput = $this->template->render('list/unordered', [ + 'class' => 'tabs responsivetable', + 'items' => $items, + ]); + $htmlOutput .= '<br>'; + $htmlOutput .= '<div class="tabs_contents">'; + return $htmlOutput; + } + + /** + * Displays top part of a fieldset + * + * @param string $title title of fieldset + * @param string $description description shown on top of fieldset + * @param array|null $errors error messages to display + * @param array $attributes optional extra attributes of fieldset + * + * @return string + */ + public function displayFieldsetTop( + $title = '', + $description = '', + $errors = null, + array $attributes = [] + ): string { + $this->group = 0; + + $attributes = array_merge(['class' => 'optbox'], $attributes); + + return $this->template->render('config/form_display/fieldset_top', [ + 'attributes' => $attributes, + 'title' => $title, + 'description' => $description, + 'errors' => $errors, + ]); + } + + /** + * Displays input field + * + * $opts keys: + * o doc - (string) documentation link + * o errors - error array + * o setvalue - (string) shows button allowing to set predefined value + * o show_restore_default - (boolean) whether show "restore default" button + * o userprefs_allow - whether user preferences are enabled for this field + * (null - no support, true/false - enabled/disabled) + * o userprefs_comment - (string) field comment + * o values - key - value pairs for <select> fields + * o values_escaped - (boolean) tells whether values array is already escaped + * (defaults to false) + * o values_disabled - (array)list of disabled values (keys from values) + * o comment - (string) tooltip comment + * o comment_warning - (bool) whether this comments warns about something + * + * @param string $path config option path + * @param string $name config option name + * @param string $type type of config option + * @param mixed $value current value + * @param string $description verbose description + * @param bool $valueIsDefault whether value is default + * @param array|null $opts see above description + * + * @return string + */ + public function displayInput( + $path, + $name, + $type, + $value, + $description = '', + $valueIsDefault = true, + $opts = null + ): string { + static $icons; // An array of IMG tags used further below in the function + + if (defined('TESTSUITE')) { + $icons = null; + } + + $isSetupScript = $this->config->get('is_setup'); + if ($icons === null) { // if the static variables have not been initialised + $icons = []; + // Icon definitions: + // The same indexes will be used in the $icons array. + // The first element contains the filename and the second + // element is used for the "alt" and "title" attributes. + $iconInit = [ + 'edit' => [ + 'b_edit', + '', + ], + 'help' => [ + 'b_help', + __('Documentation'), + ], + 'reload' => [ + 's_reload', + '', + ], + 'tblops' => [ + 'b_tblops', + '', + ], + ]; + if ($isSetupScript) { + // When called from the setup script, we don't have access to the + // sprite-aware getImage() function because the PMA_theme class + // has not been loaded, so we generate the img tags manually. + foreach ($iconInit as $k => $v) { + $title = ''; + if (! empty($v[1])) { + $title = ' title="' . $v[1] . '"'; + } + $icons[$k] = sprintf( + '<img alt="%s" src="%s"%s>', + $v[1], + "../themes/pmahomme/img/{$v[0]}.png", + $title + ); + } + } else { + // In this case we just use getImage() because it's available + foreach ($iconInit as $k => $v) { + $icons[$k] = Util::getImage( + $v[0], + $v[1] + ); + } + } + } + $hasErrors = isset($opts['errors']) && ! empty($opts['errors']); + $optionIsDisabled = ! $isSetupScript && isset($opts['userprefs_allow']) + && ! $opts['userprefs_allow']; + $nameId = 'name="' . htmlspecialchars($path) . '" id="' + . htmlspecialchars($path) . '"'; + $fieldClass = $type == 'checkbox' ? 'checkbox' : ''; + if (! $valueIsDefault) { + $fieldClass .= ($fieldClass == '' ? '' : ' ') + . ($hasErrors ? 'custom field-error' : 'custom'); + } + $fieldClass = $fieldClass ? ' class="' . $fieldClass . '"' : ''; + $trClass = $this->group > 0 + ? 'group-field group-field-' . $this->group + : ''; + if (isset($opts['setvalue']) && $opts['setvalue'] == ':group') { + unset($opts['setvalue']); + $this->group++; + $trClass = 'group-header-field group-header-' . $this->group; + } + if ($optionIsDisabled) { + $trClass .= ($trClass ? ' ' : '') . 'disabled-field'; + } + $trClass = $trClass ? ' class="' . $trClass . '"' : ''; + + $htmlOutput = '<tr' . $trClass . '>'; + $htmlOutput .= '<th>'; + $htmlOutput .= '<label for="' . htmlspecialchars($path) . '">' . htmlspecialchars_decode($name) + . '</label>'; + + if (! empty($opts['doc'])) { + $htmlOutput .= '<span class="doc">'; + $htmlOutput .= '<a href="' . $opts['doc'] + . '" target="documentation">' . $icons['help'] . '</a>'; + $htmlOutput .= "\n"; + $htmlOutput .= '</span>'; + } + + if ($optionIsDisabled) { + $htmlOutput .= '<span class="disabled-notice" title="'; + $htmlOutput .= __( + 'This setting is disabled, it will not be applied to your configuration.' + ); + $htmlOutput .= '">' . __('Disabled') . "</span>"; + } + + if (! empty($description)) { + $htmlOutput .= '<small>' . $description . '</small>'; + } + + $htmlOutput .= '</th>'; + $htmlOutput .= '<td>'; + + switch ($type) { + case 'text': + $htmlOutput .= '<input type="text" class="all85" ' . $nameId . $fieldClass + . ' value="' . htmlspecialchars($value) . '">'; + break; + case 'password': + $htmlOutput .= '<input type="password" class="all85" ' . $nameId . $fieldClass + . ' value="' . htmlspecialchars($value) . '">'; + break; + case 'short_text': + // As seen in the reporting server (#15042) we sometimes receive + // an array here. No clue about its origin nor content, so let's avoid + // a notice on htmlspecialchars(). + if (! is_array($value)) { + $htmlOutput .= '<input type="text" size="25" ' . $nameId + . $fieldClass . ' value="' . htmlspecialchars($value) + . '">'; + } + break; + case 'number_text': + $htmlOutput .= '<input type="number" ' . $nameId . $fieldClass + . ' value="' . htmlspecialchars((string) $value) . '">'; + break; + case 'checkbox': + $htmlOutput .= '<span' . $fieldClass . '><input type="checkbox" ' . $nameId + . ($value ? ' checked="checked"' : '') . '></span>'; + break; + case 'select': + $htmlOutput .= '<select class="all85" ' . $nameId . $fieldClass . '>'; + $escape = ! (isset($opts['values_escaped']) && $opts['values_escaped']); + $valuesDisabled = isset($opts['values_disabled']) + ? array_flip($opts['values_disabled']) : []; + foreach ($opts['values'] as $optValueKey => $optValue) { + // set names for boolean values + if (is_bool($optValue)) { + $optValue = mb_strtolower( + $optValue ? __('Yes') : __('No') + ); + } + // escape if necessary + if ($escape) { + $display = htmlspecialchars((string) $optValue); + $displayValue = htmlspecialchars((string) $optValueKey); + } else { + $display = $optValue; + $displayValue = $optValueKey; + } + // compare with selected value + // boolean values are cast to integers when used as array keys + $selected = is_bool($value) + ? (int) $value === $optValueKey + : $optValueKey === $value; + $htmlOutput .= '<option value="' . $displayValue . '"'; + if ($selected) { + $htmlOutput .= ' selected="selected"'; + } + if (isset($valuesDisabled[$optValueKey])) { + $htmlOutput .= ' disabled="disabled"'; + } + $htmlOutput .= '>' . $display . '</option>'; + } + $htmlOutput .= '</select>'; + break; + case 'list': + $htmlOutput .= '<textarea cols="35" rows="5" ' . $nameId . $fieldClass + . '>' . htmlspecialchars(implode("\n", $value)) . '</textarea>'; + break; + } + if ($isSetupScript + && isset($opts['userprefs_comment']) + && $opts['userprefs_comment'] + ) { + $htmlOutput .= '<a class="userprefs-comment" title="' + . htmlspecialchars($opts['userprefs_comment']) . '">' + . $icons['tblops'] . '</a>'; + } + if (isset($opts['setvalue']) && $opts['setvalue']) { + $htmlOutput .= '<a class="set-value hide" href="#' + . htmlspecialchars("$path={$opts['setvalue']}") . '" title="' + . sprintf(__('Set value: %s'), htmlspecialchars($opts['setvalue'])) + . '">' . $icons['edit'] . '</a>'; + } + if (isset($opts['show_restore_default']) && $opts['show_restore_default']) { + $htmlOutput .= '<a class="restore-default hide" href="#' . $path . '" title="' + . __('Restore default value') . '">' . $icons['reload'] . '</a>'; + } + // this must match with displayErrors() in scripts/config.js + if ($hasErrors) { + $htmlOutput .= "\n <dl class=\"inline_errors\">"; + foreach ($opts['errors'] as $error) { + $htmlOutput .= '<dd>' . htmlspecialchars($error) . '</dd>'; + } + $htmlOutput .= '</dl>'; + } + $htmlOutput .= '</td>'; + if ($isSetupScript && isset($opts['userprefs_allow'])) { + $htmlOutput .= '<td class="userprefs-allow" title="' . + __('Allow users to customize this value') . '">'; + $htmlOutput .= '<input type="checkbox" name="' . $path + . '-userprefs-allow" '; + if ($opts['userprefs_allow']) { + $htmlOutput .= 'checked="checked"'; + } + $htmlOutput .= '>'; + $htmlOutput .= '</td>'; + } elseif ($isSetupScript) { + $htmlOutput .= '<td> </td>'; + } + $htmlOutput .= '</tr>'; + return $htmlOutput; + } + + /** + * Display group header + * + * @param string $headerText Text of header + * + * @return string + */ + public function displayGroupHeader(string $headerText): string + { + $this->group++; + if ($headerText === '') { + return ''; + } + $colspan = $this->config->get('is_setup') ? 3 : 2; + + return $this->template->render('config/form_display/group_header', [ + 'group' => $this->group, + 'colspan' => $colspan, + 'header_text' => $headerText, + ]); + } + + /** + * Display group footer + * + * @return void + */ + public function displayGroupFooter(): void + { + $this->group--; + } + + /** + * Displays bottom part of a fieldset + * + * @param bool $showButtons Whether show submit and reset button + * + * @return string + */ + public function displayFieldsetBottom(bool $showButtons = true): string + { + return $this->template->render('config/form_display/fieldset_bottom', [ + 'show_buttons' => $showButtons, + 'is_setup' => $this->config->get('is_setup'), + ]); + } + + /** + * Closes form tabs + * + * @return string + */ + public function displayTabsBottom(): string + { + return $this->template->render('config/form_display/tabs_bottom'); + } + + /** + * Displays bottom part of the form + * + * @return string + */ + public function displayFormBottom(): string + { + return $this->template->render('config/form_display/form_bottom'); + } + + /** + * Appends JS validation code to $js_array + * + * @param string $fieldId ID of field to validate + * @param string|array $validators validators callback + * @param array $jsArray will be updated with javascript code + * + * @return void + */ + public function addJsValidate($fieldId, $validators, array &$jsArray): void + { + foreach ((array) $validators as $validator) { + $validator = (array) $validator; + $vName = array_shift($validator); + $vArgs = []; + foreach ($validator as $arg) { + $vArgs[] = Sanitize::escapeJsString($arg); + } + $vArgs = $vArgs ? ", ['" . implode("', '", $vArgs) . "']" : ''; + $jsArray[] = "registerFieldValidator('$fieldId', '$vName', true$vArgs)"; + } + } + + /** + * Displays JavaScript code + * + * @param array $jsArray lines of javascript code + * + * @return string + */ + public function displayJavascript(array $jsArray): string + { + if (empty($jsArray)) { + return ''; + } + + return $this->template->render('javascript/display', [ + 'js_array' => $jsArray, + ]); + } + + /** + * Displays error list + * + * @param string $name Name of item with errors + * @param array $errorList List of errors to show + * + * @return string HTML for errors + */ + public function displayErrors($name, array $errorList): string + { + return $this->template->render('config/form_display/errors', [ + 'name' => $name, + 'error_list' => $errorList, + ]); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/BaseForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/BaseForm.php new file mode 100644 index 0000000..2049070 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/BaseForm.php @@ -0,0 +1,89 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Base class for preferences. + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms; + +use PhpMyAdmin\Config\ConfigFile; +use PhpMyAdmin\Config\FormDisplay; + +/** + * Base form for user preferences + * + * @package PhpMyAdmin + */ +abstract class BaseForm extends FormDisplay +{ + /** + * Constructor + * + * @param ConfigFile $cf Config file instance + * @param int|null $serverId 0 if new server, validation; >= 1 if editing a server + */ + public function __construct(ConfigFile $cf, $serverId = null) + { + parent::__construct($cf); + foreach (static::getForms() as $formName => $form) { + $this->registerForm($formName, $form, $serverId); + } + } + + /** + * List of available forms, each form is described as an array of fields to display. + * Fields MUST have their counterparts in the $cfg array. + * + * To define form field, use the notation below: + * $forms['Form group']['Form name'] = array('Option/path'); + * + * You can assign default values set by special button ("set value: ..."), eg.: + * 'Servers/1/pmadb' => 'phpmyadmin' + * + * To group options, use: + * ':group:' . __('group name') // just define a group + * or + * 'option' => ':group' // group starting from this option + * End group blocks with: + * ':group:end' + * + * @todo This should be abstract, but that does not work in PHP 5 + * + * @return array + */ + public static function getForms() + { + return []; + } + + /** + * Returns list of fields used in the form. + * + * @return string[] + */ + public static function getFields() + { + $names = []; + foreach (static::getForms() as $form) { + foreach ($form as $k => $v) { + $names[] = is_int($k) ? $v : $k; + } + } + return $names; + } + + /** + * Returns name of the form + * + * @todo This should be abstract, but that does not work in PHP 5 + * + * @return string + */ + public static function getName() + { + return ''; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/BaseFormList.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/BaseFormList.php new file mode 100644 index 0000000..f4a5d32 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/BaseFormList.php @@ -0,0 +1,150 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms; + +use PhpMyAdmin\Config\ConfigFile; + +/** + * Class BaseFormList + * @package PhpMyAdmin\Config\Forms + */ +class BaseFormList +{ + /** + * List of all forms + */ + protected static $all = []; + + /** + * @var string + */ + protected static $ns = 'PhpMyAdmin\\Config\\Forms\\'; + + /** + * @var array + */ + private $_forms; + + /** + * @return array + */ + public static function getAll() + { + return static::$all; + } + + /** + * @param string $name Name + * @return bool + */ + public static function isValid($name) + { + return in_array($name, static::$all); + } + + /** + * @param string $name Name + * @return null|string + */ + public static function get($name) + { + if (static::isValid($name)) { + return static::$ns . $name . 'Form'; + } + return null; + } + + /** + * Constructor + * + * @param ConfigFile $cf Config file instance + */ + public function __construct(ConfigFile $cf) + { + $this->_forms = []; + foreach (static::$all as $form) { + $class = static::get($form); + $this->_forms[] = new $class($cf); + } + } + + /** + * Processes forms, returns true on successful save + * + * @param bool $allowPartialSave allows for partial form saving + * on failed validation + * @param bool $checkFormSubmit whether check for $_POST['submit_save'] + * + * @return boolean whether processing was successful + */ + public function process($allowPartialSave = true, $checkFormSubmit = true) + { + $ret = true; + foreach ($this->_forms as $form) { + $ret = $ret && $form->process($allowPartialSave, $checkFormSubmit); + } + return $ret; + } + + /** + * Displays errors + * + * @return string HTML for errors + */ + public function displayErrors() + { + $ret = ''; + foreach ($this->_forms as $form) { + $ret .= $form->displayErrors(); + } + return $ret; + } + + /** + * Reverts erroneous fields to their default values + * + * @return void + */ + public function fixErrors() + { + foreach ($this->_forms as $form) { + $form->fixErrors(); + } + } + + /** + * Tells whether form validation failed + * + * @return boolean + */ + public function hasErrors() + { + $ret = false; + foreach ($this->_forms as $form) { + $ret = $ret || $form->hasErrors(); + } + return $ret; + } + + /** + * Returns list of fields used in the form. + * + * @return string[] + */ + public static function getFields() + { + $names = []; + foreach (static::$all as $form) { + $class = static::get($form); + $names = array_merge($names, $class::getFields()); + } + return $names; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/BrowseForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/BrowseForm.php new file mode 100644 index 0000000..eee578a --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/BrowseForm.php @@ -0,0 +1,30 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Page; + +use PhpMyAdmin\Config\Forms\BaseForm; +use PhpMyAdmin\Config\Forms\User\MainForm; + +/** + * Class BrowseForm + * @package PhpMyAdmin\Config\Forms\Page + */ +class BrowseForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + return [ + 'Browse' => MainForm::getForms()['Browse'], + ]; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/DbStructureForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/DbStructureForm.php new file mode 100644 index 0000000..4f9a8e4 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/DbStructureForm.php @@ -0,0 +1,30 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Page; + +use PhpMyAdmin\Config\Forms\BaseForm; +use PhpMyAdmin\Config\Forms\User\MainForm; + +/** + * Class DbStructureForm + * @package PhpMyAdmin\Config\Forms\Page + */ +class DbStructureForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + return [ + 'DbStructure' => MainForm::getForms()['DbStructure'], + ]; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/EditForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/EditForm.php new file mode 100644 index 0000000..ad2fd46 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/EditForm.php @@ -0,0 +1,32 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Page; + +use PhpMyAdmin\Config\Forms\BaseForm; +use PhpMyAdmin\Config\Forms\User\FeaturesForm; +use PhpMyAdmin\Config\Forms\User\MainForm; + +/** + * Class EditForm + * @package PhpMyAdmin\Config\Forms\Page + */ +class EditForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + return [ + 'Edit' => MainForm::getForms()['Edit'], + 'Text_fields' => FeaturesForm::getForms()['Text_fields'], + ]; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/ExportForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/ExportForm.php new file mode 100644 index 0000000..584b2fd --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/ExportForm.php @@ -0,0 +1,18 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Page; + +/** + * Class ExportForm + * @package PhpMyAdmin\Config\Forms\Page + */ +class ExportForm extends \PhpMyAdmin\Config\Forms\User\ExportForm +{ +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/ImportForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/ImportForm.php new file mode 100644 index 0000000..78e429a --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/ImportForm.php @@ -0,0 +1,18 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Page; + +/** + * Class ImportForm + * @package PhpMyAdmin\Config\Forms\Page + */ +class ImportForm extends \PhpMyAdmin\Config\Forms\User\ImportForm +{ +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/NaviForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/NaviForm.php new file mode 100644 index 0000000..02350eb --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/NaviForm.php @@ -0,0 +1,18 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Page; + +/** + * Class NaviForm + * @package PhpMyAdmin\Config\Forms\Page + */ +class NaviForm extends \PhpMyAdmin\Config\Forms\User\NaviForm +{ +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/PageFormList.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/PageFormList.php new file mode 100644 index 0000000..f4cae3d --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/PageFormList.php @@ -0,0 +1,37 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Page preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Page; + +use PhpMyAdmin\Config\Forms\BaseFormList; + +/** + * Class PageFormList + * @package PhpMyAdmin\Config\Forms\Page + */ +class PageFormList extends BaseFormList +{ + /** + * @var array + */ + protected static $all = [ + 'Browse', + 'DbStructure', + 'Edit', + 'Export', + 'Import', + 'Navi', + 'Sql', + 'TableStructure', + ]; + /** + * @var string + */ + protected static $ns = '\\PhpMyAdmin\\Config\\Forms\\Page\\'; +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/SqlForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/SqlForm.php new file mode 100644 index 0000000..4ed85ff --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/SqlForm.php @@ -0,0 +1,18 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Page; + +/** + * Class SqlForm + * @package PhpMyAdmin\Config\Forms\Page + */ +class SqlForm extends \PhpMyAdmin\Config\Forms\User\SqlForm +{ +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/TableStructureForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/TableStructureForm.php new file mode 100644 index 0000000..05af064 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Page/TableStructureForm.php @@ -0,0 +1,30 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Page; + +use PhpMyAdmin\Config\Forms\BaseForm; +use PhpMyAdmin\Config\Forms\User\MainForm; + +/** + * Class TableStructureForm + * @package PhpMyAdmin\Config\Forms\Page + */ +class TableStructureForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + return [ + 'TableStructure' => MainForm::getForms()['TableStructure'], + ]; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ConfigForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ConfigForm.php new file mode 100644 index 0000000..6fd4515 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ConfigForm.php @@ -0,0 +1,32 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Setup; + +use PhpMyAdmin\Config\Forms\BaseForm; + +/** + * Class ConfigForm + * @package PhpMyAdmin\Config\Forms\Setup + */ +class ConfigForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + return [ + 'Config' => [ + 'DefaultLang', + 'ServerDefault', + ], + ]; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ExportForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ExportForm.php new file mode 100644 index 0000000..adf7ce4 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ExportForm.php @@ -0,0 +1,18 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Setup; + +/** + * Class ExportForm + * @package PhpMyAdmin\Config\Forms\Setup + */ +class ExportForm extends \PhpMyAdmin\Config\Forms\User\ExportForm +{ +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/FeaturesForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/FeaturesForm.php new file mode 100644 index 0000000..54f6cf2 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/FeaturesForm.php @@ -0,0 +1,77 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Setup; + +/** + * Class FeaturesForm + * @package PhpMyAdmin\Config\Forms\Setup + */ +class FeaturesForm extends \PhpMyAdmin\Config\Forms\User\FeaturesForm +{ + /** + * @return array + */ + public static function getForms() + { + // phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified,Squiz.Arrays.ArrayDeclaration.NoKeySpecified + $result = parent::getForms(); + /* Remove only_db/hide_db, we have proper Server form in setup */ + $result['Databases'] = array_diff( + $result['Databases'], + [ + 'Servers/1/only_db', + 'Servers/1/hide_db', + ] + ); + /* Following are not available to user */ + $result['Import_export'] = [ + 'UploadDir', + 'SaveDir', + 'RecodingEngine' => ':group', + 'IconvExtraParams', + ':group:end', + 'ZipDump', + 'GZipDump', + 'BZipDump', + 'CompressOnFly', + ]; + $result['Security'] = [ + 'blowfish_secret', + 'CheckConfigurationPermissions', + 'TrustedProxies', + 'AllowUserDropDatabase', + 'AllowArbitraryServer', + 'ArbitraryServerRegexp', + 'LoginCookieRecall', + 'LoginCookieStore', + 'LoginCookieDeleteAll', + 'CaptchaLoginPublicKey', + 'CaptchaLoginPrivateKey', + ]; + $result['Developer'] = [ + 'UserprefsDeveloperTab', + 'DBG/sql', + ]; + $result['Other_core_settings'] = [ + 'OBGzip', + 'PersistentConnections', + 'ExecTimeLimit', + 'MemoryLimit', + 'UseDbSearch', + 'ProxyUrl', + 'ProxyUser', + 'ProxyPass', + 'AllowThirdPartyFraming', + 'ZeroConf', + ]; + return $result; + // phpcs:enable + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ImportForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ImportForm.php new file mode 100644 index 0000000..06adf35 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ImportForm.php @@ -0,0 +1,18 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Setup; + +/** + * Class ImportForm + * @package PhpMyAdmin\Config\Forms\Setup + */ +class ImportForm extends \PhpMyAdmin\Config\Forms\User\ImportForm +{ +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/MainForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/MainForm.php new file mode 100644 index 0000000..ebdc1cd --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/MainForm.php @@ -0,0 +1,29 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Setup; + +/** + * Class MainForm + * @package PhpMyAdmin\Config\Forms\Setup + */ +class MainForm extends \PhpMyAdmin\Config\Forms\User\MainForm +{ + /** + * @return array + */ + public static function getForms() + { + $result = parent::getForms(); + /* Following are not available to user */ + $result['Startup'][] = 'ShowPhpInfo'; + $result['Startup'][] = 'ShowChgPassword'; + return $result; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/NaviForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/NaviForm.php new file mode 100644 index 0000000..da1e9ed --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/NaviForm.php @@ -0,0 +1,18 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Setup; + +/** + * Class NaviForm + * @package PhpMyAdmin\Config\Forms\Setup + */ +class NaviForm extends \PhpMyAdmin\Config\Forms\User\NaviForm +{ +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ServersForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ServersForm.php new file mode 100644 index 0000000..553447f --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/ServersForm.php @@ -0,0 +1,116 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Setup; + +use PhpMyAdmin\Config\Forms\BaseForm; + +/** + * Class ServersForm + * @package PhpMyAdmin\Config\Forms\Setup + */ +class ServersForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + // phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified,Squiz.Arrays.ArrayDeclaration.NoKeySpecified + return [ + 'Server' => [ + 'Servers' => [ + 1 => [ + 'verbose', + 'host', + 'port', + 'socket', + 'ssl', + 'compress', + ], + ], + ], + 'Server_auth' => [ + 'Servers' => [ + 1 => [ + 'auth_type', + ':group:' . __('Config authentication'), + 'user', + 'password', + ':group:end', + ':group:' . __('HTTP authentication'), + 'auth_http_realm', + ':group:end', + ':group:' . __('Signon authentication'), + 'SignonSession', + 'SignonURL', + 'LogoutURL', + ], + ], + ], + 'Server_config' => [ + 'Servers' => [ + 1 => [ + 'only_db', + 'hide_db', + 'AllowRoot', + 'AllowNoPassword', + 'DisableIS', + 'AllowDeny/order', + 'AllowDeny/rules', + 'SessionTimeZone', + ], + ], + ], + 'Server_pmadb' => [ + 'Servers' => [ + 1 => [ + 'pmadb' => 'phpmyadmin', + 'controlhost', + 'controlport', + 'controluser', + 'controlpass', + 'bookmarktable' => 'pma__bookmark', + 'relation' => 'pma__relation', + 'userconfig' => 'pma__userconfig', + 'users' => 'pma__users', + 'usergroups' => 'pma__usergroups', + 'navigationhiding' => 'pma__navigationhiding', + 'table_info' => 'pma__table_info', + 'column_info' => 'pma__column_info', + 'history' => 'pma__history', + 'recent' => 'pma__recent', + 'favorite' => 'pma__favorite', + 'table_uiprefs' => 'pma__table_uiprefs', + 'tracking' => 'pma__tracking', + 'table_coords' => 'pma__table_coords', + 'pdf_pages' => 'pma__pdf_pages', + 'savedsearches' => 'pma__savedsearches', + 'central_columns' => 'pma__central_columns', + 'designer_settings' => 'pma__designer_settings', + 'export_templates' => 'pma__export_templates', + 'MaxTableUiprefs' => 100, + ], + ], + ], + 'Server_tracking' => [ + 'Servers' => [ + 1 => [ + 'tracking_version_auto_create', + 'tracking_default_statements', + 'tracking_add_drop_view', + 'tracking_add_drop_table', + 'tracking_add_drop_database', + ], + ], + ], + ]; + // phpcs:enable + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/SetupFormList.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/SetupFormList.php new file mode 100644 index 0000000..91edb27 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/SetupFormList.php @@ -0,0 +1,37 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Setup preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Setup; + +use PhpMyAdmin\Config\Forms\BaseFormList; + +/** + * Class SetupFormList + * @package PhpMyAdmin\Config\Forms\Setup + */ +class SetupFormList extends BaseFormList +{ + /** + * @var array + */ + protected static $all = [ + 'Config', + 'Export', + 'Features', + 'Import', + 'Main', + 'Navi', + 'Servers', + 'Sql', + ]; + /** + * @var string + */ + protected static $ns = '\\PhpMyAdmin\\Config\\Forms\\Setup\\'; +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/SqlForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/SqlForm.php new file mode 100644 index 0000000..0cc3a1c --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/Setup/SqlForm.php @@ -0,0 +1,28 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\Setup; + +/** + * Class SqlForm + * @package PhpMyAdmin\Config\Forms\Setup + */ +class SqlForm extends \PhpMyAdmin\Config\Forms\User\SqlForm +{ + /** + * @return array + */ + public static function getForms() + { + $result = parent::getForms(); + /* Following are not available to user */ + $result['Sql_queries'][] = 'QueryHistoryDB'; + return $result; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/User/ExportForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/ExportForm.php new file mode 100644 index 0000000..f2f853c --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/ExportForm.php @@ -0,0 +1,160 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\User; + +use PhpMyAdmin\Config\Forms\BaseForm; + +/** + * Class ExportForm + * @package PhpMyAdmin\Config\Forms\User + */ +class ExportForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + // phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified,Squiz.Arrays.ArrayDeclaration.NoKeySpecified + return [ + 'Export_defaults' => [ + 'Export/method', + ':group:' . __('Quick'), + 'Export/quick_export_onserver', + 'Export/quick_export_onserver_overwrite', + ':group:end', + ':group:' . __('Custom'), + 'Export/format', + 'Export/compression', + 'Export/charset', + 'Export/lock_tables', + 'Export/as_separate_files', + 'Export/asfile' => ':group', + 'Export/onserver', + 'Export/onserver_overwrite', + ':group:end', + 'Export/file_template_table', + 'Export/file_template_database', + 'Export/file_template_server', + ], + 'Sql' => [ + 'Export/sql_include_comments' => ':group', + 'Export/sql_dates', + 'Export/sql_relation', + 'Export/sql_mime', + ':group:end', + 'Export/sql_use_transaction', + 'Export/sql_disable_fk', + 'Export/sql_views_as_tables', + 'Export/sql_metadata', + 'Export/sql_compatibility', + 'Export/sql_structure_or_data', + ':group:' . __('Structure'), + 'Export/sql_drop_database', + 'Export/sql_create_database', + 'Export/sql_drop_table', + 'Export/sql_create_table' => ':group', + 'Export/sql_if_not_exists', + 'Export/sql_auto_increment', + ':group:end', + 'Export/sql_create_view' => ':group', + 'Export/sql_view_current_user', + 'Export/sql_or_replace_view', + ':group:end', + 'Export/sql_procedure_function', + 'Export/sql_create_trigger', + 'Export/sql_backquotes', + ':group:end', + ':group:' . __('Data'), + 'Export/sql_delayed', + 'Export/sql_ignore', + 'Export/sql_type', + 'Export/sql_insert_syntax', + 'Export/sql_max_query_size', + 'Export/sql_hex_for_binary', + 'Export/sql_utc_time', + ], + 'CodeGen' => [ + 'Export/codegen_format', + ], + 'Csv' => [ + ':group:' . __('CSV'), + 'Export/csv_separator', + 'Export/csv_enclosed', + 'Export/csv_escaped', + 'Export/csv_terminated', + 'Export/csv_null', + 'Export/csv_removeCRLF', + 'Export/csv_columns', + ':group:end', + ':group:' . __('CSV for MS Excel'), + 'Export/excel_null', + 'Export/excel_removeCRLF', + 'Export/excel_columns', + 'Export/excel_edition', + ], + 'Latex' => [ + 'Export/latex_caption', + 'Export/latex_structure_or_data', + ':group:' . __('Structure'), + 'Export/latex_structure_caption', + 'Export/latex_structure_continued_caption', + 'Export/latex_structure_label', + 'Export/latex_relation', + 'Export/latex_comments', + 'Export/latex_mime', + ':group:end', + ':group:' . __('Data'), + 'Export/latex_columns', + 'Export/latex_data_caption', + 'Export/latex_data_continued_caption', + 'Export/latex_data_label', + 'Export/latex_null', + ], + 'Microsoft_Office' => [ + ':group:' . __('Microsoft Word 2000'), + 'Export/htmlword_structure_or_data', + 'Export/htmlword_null', + 'Export/htmlword_columns', + ], + 'Open_Document' => [ + ':group:' . __('OpenDocument Spreadsheet'), + 'Export/ods_columns', + 'Export/ods_null', + ':group:end', + ':group:' . __('OpenDocument Text'), + 'Export/odt_structure_or_data', + ':group:' . __('Structure'), + 'Export/odt_relation', + 'Export/odt_comments', + 'Export/odt_mime', + ':group:end', + ':group:' . __('Data'), + 'Export/odt_columns', + 'Export/odt_null', + ], + 'Texy' => [ + 'Export/texytext_structure_or_data', + ':group:' . __('Data'), + 'Export/texytext_null', + 'Export/texytext_columns', + ], + ]; + // phpcs:enable + } + + /** + * @return string + */ + public static function getName() + { + return __('Export'); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/User/FeaturesForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/FeaturesForm.php new file mode 100644 index 0000000..58d3c24 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/FeaturesForm.php @@ -0,0 +1,95 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\User; + +use PhpMyAdmin\Config\Forms\BaseForm; + +/** + * Class FeaturesForm + * @package PhpMyAdmin\Config\Forms\User + */ +class FeaturesForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + $result = [ + 'General' => [ + 'VersionCheck', + 'NaturalOrder', + 'InitialSlidersState', + 'SkipLockedTables', + 'DisableMultiTableMaintenance', + 'ShowHint', + 'SendErrorReports', + 'ConsoleEnterExecutes', + 'DisableShortcutKeys', + ], + 'Databases' => [ + 'Servers/1/only_db', // saves to Server/only_db + 'Servers/1/hide_db', // saves to Server/hide_db + 'MaxDbList', + 'MaxTableList', + 'DefaultConnectionCollation', + ], + 'Text_fields' => [ + 'CharEditing', + 'MinSizeForInputField', + 'MaxSizeForInputField', + 'CharTextareaCols', + 'CharTextareaRows', + 'TextareaCols', + 'TextareaRows', + 'LongtextDoubleTextarea', + ], + 'Page_titles' => [ + 'TitleDefault', + 'TitleTable', + 'TitleDatabase', + 'TitleServer', + ], + 'Warnings' => [ + 'PmaNoRelation_DisableWarning', + 'SuhosinDisableWarning', + 'LoginCookieValidityDisableWarning', + 'ReservedWordDisableWarning', + ], + 'Console' => [ + 'Console/Mode', + 'Console/StartHistory', + 'Console/AlwaysExpand', + 'Console/CurrentQuery', + 'Console/EnterExecutes', + 'Console/DarkTheme', + 'Console/Height', + 'Console/GroupQueries', + 'Console/OrderBy', + 'Console/Order', + ], + ]; + // skip Developer form if no setting is available + if ($GLOBALS['cfg']['UserprefsDeveloperTab']) { + $result['Developer'] = [ + 'DBG/sql', + ]; + } + return $result; + } + + /** + * @return string + */ + public static function getName() + { + return __('Features'); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/User/ImportForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/ImportForm.php new file mode 100644 index 0000000..c447567 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/ImportForm.php @@ -0,0 +1,73 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\User; + +use PhpMyAdmin\Config\Forms\BaseForm; + +/** + * Class ImportForm + * @package PhpMyAdmin\Config\Forms\User + */ +class ImportForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + return [ + 'Import_defaults' => [ + 'Import/format', + 'Import/charset', + 'Import/allow_interrupt', + 'Import/skip_queries', + 'enable_drag_drop_import', + ], + 'Sql' => [ + 'Import/sql_compatibility', + 'Import/sql_no_auto_value_on_zero', + 'Import/sql_read_as_multibytes', + ], + 'Csv' => [ + ':group:' . __('CSV'), + 'Import/csv_replace', + 'Import/csv_ignore', + 'Import/csv_terminated', + 'Import/csv_enclosed', + 'Import/csv_escaped', + 'Import/csv_col_names', + ':group:end', + ':group:' . __('CSV using LOAD DATA'), + 'Import/ldi_replace', + 'Import/ldi_ignore', + 'Import/ldi_terminated', + 'Import/ldi_enclosed', + 'Import/ldi_escaped', + 'Import/ldi_local_option', + ], + 'Open_Document' => [ + ':group:' . __('OpenDocument Spreadsheet'), + 'Import/ods_col_names', + 'Import/ods_empty_rows', + 'Import/ods_recognize_percentages', + 'Import/ods_recognize_currency', + ], + + ]; + } + + /** + * @return string + */ + public static function getName() + { + return __('Import'); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/User/MainForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/MainForm.php new file mode 100644 index 0000000..907bbaa --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/MainForm.php @@ -0,0 +1,98 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\User; + +use PhpMyAdmin\Config\Forms\BaseForm; + +/** + * Class MainForm + * @package PhpMyAdmin\Config\Forms\User + */ +class MainForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + return [ + 'Startup' => [ + 'ShowCreateDb', + 'ShowStats', + 'ShowServerInfo', + ], + 'DbStructure' => [ + 'ShowDbStructureCharset', + 'ShowDbStructureComment', + 'ShowDbStructureCreation', + 'ShowDbStructureLastUpdate', + 'ShowDbStructureLastCheck', + ], + 'TableStructure' => [ + 'HideStructureActions', + 'ShowColumnComments', + ':group:' . __('Default transformations'), + 'DefaultTransformations/Hex', + 'DefaultTransformations/Substring', + 'DefaultTransformations/Bool2Text', + 'DefaultTransformations/External', + 'DefaultTransformations/PreApPend', + 'DefaultTransformations/DateFormat', + 'DefaultTransformations/Inline', + 'DefaultTransformations/TextImageLink', + 'DefaultTransformations/TextLink', + ':group:end', + ], + 'Browse' => [ + 'TableNavigationLinksMode', + 'ActionLinksMode', + 'ShowAll', + 'MaxRows', + 'Order', + 'BrowsePointerEnable', + 'BrowseMarkerEnable', + 'GridEditing', + 'SaveCellsAtOnce', + 'RepeatCells', + 'LimitChars', + 'RowActionLinks', + 'RowActionLinksWithoutUnique', + 'TablePrimaryKeyOrder', + 'RememberSorting', + 'RelationalDisplay', + ], + 'Edit' => [ + 'ProtectBinary', + 'ShowFunctionFields', + 'ShowFieldTypesInDataEditView', + 'InsertRows', + 'ForeignKeyDropdownOrder', + 'ForeignKeyMaxLimit', + ], + 'Tabs' => [ + 'TabsMode', + 'DefaultTabServer', + 'DefaultTabDatabase', + 'DefaultTabTable', + ], + 'DisplayRelationalSchema' => [ + 'PDFDefaultPageSize', + ], + ]; + } + + /** + * @return string + */ + public static function getName() + { + return __('Main panel'); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/User/NaviForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/NaviForm.php new file mode 100644 index 0000000..e2d373c --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/NaviForm.php @@ -0,0 +1,74 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\User; + +use PhpMyAdmin\Config\Forms\BaseForm; + +/** + * Class NaviForm + * @package PhpMyAdmin\Config\Forms\User + */ +class NaviForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + return [ + 'Navi_panel' => [ + 'ShowDatabasesNavigationAsTree', + 'NavigationLinkWithMainPanel', + 'NavigationDisplayLogo', + 'NavigationLogoLink', + 'NavigationLogoLinkWindow', + 'NavigationTreePointerEnable', + 'FirstLevelNavigationItems', + 'NavigationTreeDisplayItemFilterMinimum', + 'NumRecentTables', + 'NumFavoriteTables', + 'NavigationWidth', + ], + 'Navi_tree' => [ + 'MaxNavigationItems', + 'NavigationTreeEnableGrouping', + 'NavigationTreeEnableExpansion', + 'NavigationTreeShowTables', + 'NavigationTreeShowViews', + 'NavigationTreeShowFunctions', + 'NavigationTreeShowProcedures', + 'NavigationTreeShowEvents', + 'NavigationTreeAutoexpandSingleDb', + ], + 'Navi_servers' => [ + 'NavigationDisplayServers', + 'DisplayServersList', + ], + 'Navi_databases' => [ + 'NavigationTreeDisplayDbFilterMinimum', + 'NavigationTreeDbSeparator', + ], + 'Navi_tables' => [ + 'NavigationTreeDefaultTabTable', + 'NavigationTreeDefaultTabTable2', + 'NavigationTreeTableSeparator', + 'NavigationTreeTableLevel', + ], + ]; + } + + /** + * @return string + */ + public static function getName() + { + return __('Navigation panel'); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/User/SqlForm.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/SqlForm.php new file mode 100644 index 0000000..3d8ac7a --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/SqlForm.php @@ -0,0 +1,54 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\User; + +use PhpMyAdmin\Config\Forms\BaseForm; + +/** + * Class SqlForm + * @package PhpMyAdmin\Config\Forms\User + */ +class SqlForm extends BaseForm +{ + /** + * @return array + */ + public static function getForms() + { + return [ + 'Sql_queries' => [ + 'ShowSQL', + 'Confirm', + 'QueryHistoryMax', + 'IgnoreMultiSubmitErrors', + 'MaxCharactersInDisplayedSQL', + 'RetainQueryBox', + 'CodemirrorEnable', + 'LintEnable', + 'EnableAutocompleteForTablesAndColumns', + 'DefaultForeignKeyChecks', + ], + 'Sql_box' => [ + 'SQLQuery/Edit', + 'SQLQuery/Explain', + 'SQLQuery/ShowAsPHP', + 'SQLQuery/Refresh', + ], + ]; + } + + /** + * @return string + */ + public static function getName() + { + return __('SQL queries'); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Forms/User/UserFormList.php b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/UserFormList.php new file mode 100644 index 0000000..92294ed --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Forms/User/UserFormList.php @@ -0,0 +1,35 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * User preferences form + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config\Forms\User; + +use PhpMyAdmin\Config\Forms\BaseFormList; + +/** + * Class UserFormList + * @package PhpMyAdmin\Config\Forms\User + */ +class UserFormList extends BaseFormList +{ + /** + * @var array + */ + protected static $all = [ + 'Features', + 'Sql', + 'Navi', + 'Main', + 'Export', + 'Import', + ]; + /** + * @var string + */ + protected static $ns = '\\PhpMyAdmin\\Config\\Forms\\User\\'; +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/PageSettings.php b/srcs/phpmyadmin/libraries/classes/Config/PageSettings.php new file mode 100644 index 0000000..57232e1 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/PageSettings.php @@ -0,0 +1,233 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Page-related settings + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Config\ConfigFile; +use PhpMyAdmin\Config\FormDisplay; +use PhpMyAdmin\Config\Forms\Page\PageFormList; +use PhpMyAdmin\Core; +use PhpMyAdmin\Message; +use PhpMyAdmin\Response; +use PhpMyAdmin\UserPreferences; + +/** + * Page-related settings + * + * @package PhpMyAdmin + */ +class PageSettings +{ + + /** + * Contains id of the form element + * @var string + */ + private $_elemId = 'page_settings_modal'; + + /** + * Name of the group to show + * @var string + */ + private $_groupName = ''; + + /** + * Contains HTML of errors + * @var string + */ + private $_errorHTML = ''; + + /** + * Contains HTML of settings + * @var string + */ + private $_HTML = ''; + + /** + * @var UserPreferences + */ + private $userPreferences; + + /** + * Constructor + * + * @param string $formGroupName The name of config form group to display + * @param string $elemId Id of the div containing settings + */ + public function __construct($formGroupName, $elemId = null) + { + $this->userPreferences = new UserPreferences(); + + $formClass = PageFormList::get($formGroupName); + if ($formClass === null) { + return; + } + + if (isset($_REQUEST['printview']) && $_REQUEST['printview'] == '1') { + return; + } + + if (! empty($elemId)) { + $this->_elemId = $elemId; + } + $this->_groupName = $formGroupName; + + $cf = new ConfigFile($GLOBALS['PMA_Config']->base_settings); + $this->userPreferences->pageInit($cf); + + $formDisplay = new $formClass($cf); + + // Process form + $error = null; + if (isset($_POST['submit_save']) + && $_POST['submit_save'] == $formGroupName + ) { + $this->_processPageSettings($formDisplay, $cf, $error); + } + + // Display forms + $this->_HTML = $this->_getPageSettingsDisplay($formDisplay, $error); + } + + /** + * Process response to form + * + * @param FormDisplay $formDisplay Form + * @param ConfigFile $cf Configuration file + * @param Message|null $error Error message + * + * @return void + */ + private function _processPageSettings(&$formDisplay, &$cf, &$error) + { + if ($formDisplay->process(false) && ! $formDisplay->hasErrors()) { + // save settings + $result = $this->userPreferences->save($cf->getConfigArray()); + if ($result === true) { + // reload page + $response = Response::getInstance(); + Core::sendHeaderLocation( + $response->getFooter()->getSelfUrl() + ); + exit; + } else { + $error = $result; + } + } + } + + /** + * Store errors in _errorHTML + * + * @param FormDisplay $formDisplay Form + * @param Message|null $error Error message + * + * @return void + */ + private function _storeError(&$formDisplay, &$error) + { + $retval = ''; + if ($error) { + $retval .= $error->getDisplay(); + } + if ($formDisplay->hasErrors()) { + // form has errors + $retval .= '<div class="error config-form">' + . '<b>' . __( + 'Cannot save settings, submitted configuration form contains ' + . 'errors!' + ) . '</b>' + . $formDisplay->displayErrors() + . '</div>'; + } + $this->_errorHTML = $retval; + } + + /** + * Display page-related settings + * + * @param FormDisplay $formDisplay Form + * @param Message $error Error message + * + * @return string + */ + private function _getPageSettingsDisplay(&$formDisplay, &$error) + { + $response = Response::getInstance(); + + $retval = ''; + + $this->_storeError($formDisplay, $error); + + $retval .= '<div id="' . $this->_elemId . '">'; + $retval .= '<div class="page_settings">'; + $retval .= $formDisplay->getDisplay( + true, + true, + false, + $response->getFooter()->getSelfUrl(), + [ + 'submit_save' => $this->_groupName, + ] + ); + $retval .= '</div>'; + $retval .= '</div>'; + + return $retval; + } + + /** + * Get HTML output + * + * @return string + */ + public function getHTML() + { + return $this->_HTML; + } + + /** + * Get error HTML output + * + * @return string + */ + public function getErrorHTML() + { + return $this->_errorHTML; + } + + /** + * Group to show for Page-related settings + * @param string $formGroupName The name of config form group to display + * @return PageSettings + */ + public static function showGroup($formGroupName) + { + $object = new PageSettings($formGroupName); + + $response = Response::getInstance(); + $response->addHTML($object->getErrorHTML()); + $response->addHTML($object->getHTML()); + + return $object; + } + + /** + * Get HTML for navigation settings + * @return string + */ + public static function getNaviSettings() + { + $object = new PageSettings('Navi', 'pma_navigation_settings'); + + $response = Response::getInstance(); + $response->addHTML($object->getErrorHTML()); + return $object->getHTML(); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/ServerConfigChecks.php b/srcs/phpmyadmin/libraries/classes/Config/ServerConfigChecks.php new file mode 100644 index 0000000..1b0ac9d --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/ServerConfigChecks.php @@ -0,0 +1,583 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Server config checks management + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Config\ConfigFile; +use PhpMyAdmin\Config\Descriptions; +use PhpMyAdmin\Core; +use PhpMyAdmin\Sanitize; +use PhpMyAdmin\Setup\Index as SetupIndex; +use PhpMyAdmin\Url; +use PhpMyAdmin\Util; + +/** + * Performs various compatibility, security and consistency checks on current config + * + * Outputs results to message list, must be called between SetupIndex::messagesBegin() + * and SetupIndex::messagesEnd() + * + * @package PhpMyAdmin + */ +class ServerConfigChecks +{ + /** + * @var ConfigFile configurations being checked + */ + protected $cfg; + + /** + * Constructor. + * + * @param ConfigFile $cfg Configuration + */ + public function __construct(ConfigFile $cfg) + { + $this->cfg = $cfg; + } + + /** + * Perform config checks + * + * @return void + */ + public function performConfigChecks() + { + $blowfishSecret = $this->cfg->get('blowfish_secret'); + $blowfishSecretSet = false; + $cookieAuthUsed = false; + + list($cookieAuthUsed, $blowfishSecret, $blowfishSecretSet) + = $this->performConfigChecksServers( + $cookieAuthUsed, + $blowfishSecret, + $blowfishSecretSet + ); + + $this->performConfigChecksCookieAuthUsed( + $cookieAuthUsed, + $blowfishSecretSet, + $blowfishSecret + ); + + // + // $cfg['AllowArbitraryServer'] + // should be disabled + // + if ($this->cfg->getValue('AllowArbitraryServer')) { + $sAllowArbitraryServerWarn = sprintf( + __( + 'This %soption%s should be disabled as it allows attackers to ' + . 'bruteforce login to any MySQL server. If you feel this is necessary, ' + . 'use %srestrict login to MySQL server%s or %strusted proxies list%s. ' + . 'However, IP-based protection with trusted proxies list may not be ' + . 'reliable if your IP belongs to an ISP where thousands of users, ' + . 'including you, are connected to.' + ), + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]', + '[/a]', + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]', + '[/a]', + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]', + '[/a]' + ); + SetupIndex::messagesSet( + 'notice', + 'AllowArbitraryServer', + Descriptions::get('AllowArbitraryServer'), + Sanitize::sanitizeMessage($sAllowArbitraryServerWarn) + ); + } + + $this->performConfigChecksLoginCookie(); + + $sDirectoryNotice = __( + 'This value should be double checked to ensure that this directory is ' + . 'neither world accessible nor readable or writable by other users on ' + . 'your server.' + ); + + // + // $cfg['SaveDir'] + // should not be world-accessible + // + if ($this->cfg->getValue('SaveDir') != '') { + SetupIndex::messagesSet( + 'notice', + 'SaveDir', + Descriptions::get('SaveDir'), + Sanitize::sanitizeMessage($sDirectoryNotice) + ); + } + + // + // $cfg['TempDir'] + // should not be world-accessible + // + if ($this->cfg->getValue('TempDir') != '') { + SetupIndex::messagesSet( + 'notice', + 'TempDir', + Descriptions::get('TempDir'), + Sanitize::sanitizeMessage($sDirectoryNotice) + ); + } + + $this->performConfigChecksZips(); + } + + /** + * Check config of servers + * + * @param boolean $cookieAuthUsed Cookie auth is used + * @param string $blowfishSecret Blowfish secret + * @param boolean $blowfishSecretSet Blowfish secret set + * + * @return array + */ + protected function performConfigChecksServers( + $cookieAuthUsed, + $blowfishSecret, + $blowfishSecretSet + ) { + $serverCnt = $this->cfg->getServerCount(); + for ($i = 1; $i <= $serverCnt; $i++) { + $cookieAuthServer + = ($this->cfg->getValue("Servers/$i/auth_type") == 'cookie'); + $cookieAuthUsed |= $cookieAuthServer; + $serverName = $this->performConfigChecksServersGetServerName( + $this->cfg->getServerName($i), + $i + ); + $serverName = htmlspecialchars($serverName); + + list($blowfishSecret, $blowfishSecretSet) + = $this->performConfigChecksServersSetBlowfishSecret( + $blowfishSecret, + $cookieAuthServer, + $blowfishSecretSet + ); + + // + // $cfg['Servers'][$i]['ssl'] + // should be enabled if possible + // + if (! $this->cfg->getValue("Servers/$i/ssl")) { + $title = Descriptions::get('Servers/1/ssl') . " ($serverName)"; + SetupIndex::messagesSet( + 'notice', + "Servers/$i/ssl", + $title, + __( + 'You should use SSL connections if your database server ' + . 'supports it.' + ) + ); + } + $sSecurityInfoMsg = Sanitize::sanitizeMessage(sprintf( + __( + 'If you feel this is necessary, use additional protection settings - ' + . '%1$shost authentication%2$s settings and %3$strusted proxies list%4%s. ' + . 'However, IP-based protection may not be reliable if your IP belongs ' + . 'to an ISP where thousands of users, including you, are connected to.' + ), + '[a@' . Url::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server_config]', + '[/a]', + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]', + '[/a]' + )); + + // + // $cfg['Servers'][$i]['auth_type'] + // warn about full user credentials if 'auth_type' is 'config' + // + if ($this->cfg->getValue("Servers/$i/auth_type") == 'config' + && $this->cfg->getValue("Servers/$i/user") != '' + && $this->cfg->getValue("Servers/$i/password") != '' + ) { + $title = Descriptions::get('Servers/1/auth_type') + . " ($serverName)"; + SetupIndex::messagesSet( + 'notice', + "Servers/$i/auth_type", + $title, + Sanitize::sanitizeMessage(sprintf( + __( + 'You set the [kbd]config[/kbd] authentication type and included ' + . 'username and password for auto-login, which is not a desirable ' + . 'option for live hosts. Anyone who knows or guesses your phpMyAdmin ' + . 'URL can directly access your phpMyAdmin panel. Set %1$sauthentication ' + . 'type%2$s to [kbd]cookie[/kbd] or [kbd]http[/kbd].' + ), + '[a@' . Url::getCommon(['page' => 'servers', 'mode' => 'edit', 'id' => $i]) . '#tab_Server]', + '[/a]' + )) + . ' ' . $sSecurityInfoMsg + ); + } + + // + // $cfg['Servers'][$i]['AllowRoot'] + // $cfg['Servers'][$i]['AllowNoPassword'] + // serious security flaw + // + if ($this->cfg->getValue("Servers/$i/AllowRoot") + && $this->cfg->getValue("Servers/$i/AllowNoPassword") + ) { + $title = Descriptions::get('Servers/1/AllowNoPassword') + . " ($serverName)"; + SetupIndex::messagesSet( + 'notice', + "Servers/$i/AllowNoPassword", + $title, + __('You allow for connecting to the server without a password.') + . ' ' . $sSecurityInfoMsg + ); + } + } + return [ + $cookieAuthUsed, + $blowfishSecret, + $blowfishSecretSet, + ]; + } + + /** + * Set blowfish secret + * + * @param string $blowfishSecret Blowfish secret + * @param boolean $cookieAuthServer Cookie auth is used + * @param boolean $blowfishSecretSet Blowfish secret set + * + * @return array + */ + protected function performConfigChecksServersSetBlowfishSecret( + $blowfishSecret, + $cookieAuthServer, + $blowfishSecretSet + ) { + if ($cookieAuthServer && $blowfishSecret === null) { + $blowfishSecretSet = true; + $this->cfg->set('blowfish_secret', Util::generateRandom(32)); + } + return [ + $blowfishSecret, + $blowfishSecretSet, + ]; + } + + /** + * Define server name + * + * @param string $serverName Server name + * @param int $serverId Server id + * + * @return string Server name + */ + protected function performConfigChecksServersGetServerName( + $serverName, + $serverId + ) { + if ($serverName == 'localhost') { + $serverName .= " [$serverId]"; + return $serverName; + } + return $serverName; + } + + /** + * Perform config checks for zip part. + * + * @return void + */ + protected function performConfigChecksZips() + { + $this->performConfigChecksServerGZipdump(); + $this->performConfigChecksServerBZipdump(); + $this->performConfigChecksServersZipdump(); + } + + /** + * Perform config checks for zip part. + * + * @return void + */ + protected function performConfigChecksServersZipdump() + { + // + // $cfg['ZipDump'] + // requires zip_open in import + // + if ($this->cfg->getValue('ZipDump') && ! $this->functionExists('zip_open')) { + SetupIndex::messagesSet( + 'error', + 'ZipDump_import', + Descriptions::get('ZipDump'), + Sanitize::sanitizeMessage(sprintf( + __( + '%sZip decompression%s requires functions (%s) which are unavailable ' + . 'on this system.' + ), + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]', + '[/a]', + 'zip_open' + )) + ); + } + + // + // $cfg['ZipDump'] + // requires gzcompress in export + // + if ($this->cfg->getValue('ZipDump') && ! $this->functionExists('gzcompress')) { + SetupIndex::messagesSet( + 'error', + 'ZipDump_export', + Descriptions::get('ZipDump'), + Sanitize::sanitizeMessage(sprintf( + __( + '%sZip compression%s requires functions (%s) which are unavailable on ' + . 'this system.' + ), + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]', + '[/a]', + 'gzcompress' + )) + ); + } + } + + /** + * Check config of servers + * + * @param boolean $cookieAuthUsed Cookie auth is used + * @param boolean $blowfishSecretSet Blowfish secret set + * @param string $blowfishSecret Blowfish secret + * + * @return void + */ + protected function performConfigChecksCookieAuthUsed( + $cookieAuthUsed, + $blowfishSecretSet, + $blowfishSecret + ) { + // + // $cfg['blowfish_secret'] + // it's required for 'cookie' authentication + // + if ($cookieAuthUsed) { + if ($blowfishSecretSet) { + // 'cookie' auth used, blowfish_secret was generated + SetupIndex::messagesSet( + 'notice', + 'blowfish_secret_created', + Descriptions::get('blowfish_secret'), + Sanitize::sanitizeMessage(__( + 'You didn\'t have blowfish secret set and have enabled ' + . '[kbd]cookie[/kbd] authentication, so a key was automatically ' + . 'generated for you. It is used to encrypt cookies; you don\'t need to ' + . 'remember it.' + )) + ); + } else { + $blowfishWarnings = []; + // check length + if (strlen($blowfishSecret) < 32) { + // too short key + $blowfishWarnings[] = __( + 'Key is too short, it should have at least 32 characters.' + ); + } + // check used characters + $hasDigits = (bool) preg_match('/\d/', $blowfishSecret); + $hasChars = (bool) preg_match('/\S/', $blowfishSecret); + $hasNonword = (bool) preg_match('/\W/', $blowfishSecret); + if (! $hasDigits || ! $hasChars || ! $hasNonword) { + $blowfishWarnings[] = Sanitize::sanitizeMessage( + __( + 'Key should contain letters, numbers [em]and[/em] ' + . 'special characters.' + ) + ); + } + if (! empty($blowfishWarnings)) { + SetupIndex::messagesSet( + 'error', + 'blowfish_warnings' . count($blowfishWarnings), + Descriptions::get('blowfish_secret'), + implode('<br>', $blowfishWarnings) + ); + } + } + } + } + + /** + * Check configuration for login cookie + * + * @return void + */ + protected function performConfigChecksLoginCookie() + { + // + // $cfg['LoginCookieValidity'] + // value greater than session.gc_maxlifetime will cause + // random session invalidation after that time + $loginCookieValidity = $this->cfg->getValue('LoginCookieValidity'); + if ($loginCookieValidity > ini_get('session.gc_maxlifetime') + ) { + SetupIndex::messagesSet( + 'error', + 'LoginCookieValidity', + Descriptions::get('LoginCookieValidity'), + Sanitize::sanitizeMessage(sprintf( + __( + '%1$sLogin cookie validity%2$s greater than %3$ssession.gc_maxlifetime%4$s may ' + . 'cause random session invalidation (currently session.gc_maxlifetime ' + . 'is %5$d).' + ), + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]', + '[/a]', + '[a@' . Core::getPHPDocLink('session.configuration.php#ini.session.gc-maxlifetime') . ']', + '[/a]', + ini_get('session.gc_maxlifetime') + )) + ); + } + + // + // $cfg['LoginCookieValidity'] + // should be at most 1800 (30 min) + // + if ($loginCookieValidity > 1800) { + SetupIndex::messagesSet( + 'notice', + 'LoginCookieValidity', + Descriptions::get('LoginCookieValidity'), + Sanitize::sanitizeMessage(sprintf( + __( + '%sLogin cookie validity%s should be set to 1800 seconds (30 minutes) ' + . 'at most. Values larger than 1800 may pose a security risk such as ' + . 'impersonation.' + ), + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]', + '[/a]' + )) + ); + } + + // + // $cfg['LoginCookieValidity'] + // $cfg['LoginCookieStore'] + // LoginCookieValidity must be less or equal to LoginCookieStore + // + if (($this->cfg->getValue('LoginCookieStore') != 0) + && ($loginCookieValidity > $this->cfg->getValue('LoginCookieStore')) + ) { + SetupIndex::messagesSet( + 'error', + 'LoginCookieValidity', + Descriptions::get('LoginCookieValidity'), + Sanitize::sanitizeMessage(sprintf( + __( + 'If using [kbd]cookie[/kbd] authentication and %sLogin cookie store%s ' + . 'is not 0, %sLogin cookie validity%s must be set to a value less or ' + . 'equal to it.' + ), + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]', + '[/a]', + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Security]', + '[/a]' + )) + ); + } + } + + /** + * Check GZipDump configuration + * + * @return void + */ + protected function performConfigChecksServerBZipdump() + { + // + // $cfg['BZipDump'] + // requires bzip2 functions + // + if ($this->cfg->getValue('BZipDump') + && (! $this->functionExists('bzopen') || ! $this->functionExists('bzcompress')) + ) { + $functions = $this->functionExists('bzopen') + ? '' : + 'bzopen'; + $functions .= $this->functionExists('bzcompress') + ? '' + : ($functions ? ', ' : '') . 'bzcompress'; + SetupIndex::messagesSet( + 'error', + 'BZipDump', + Descriptions::get('BZipDump'), + Sanitize::sanitizeMessage( + sprintf( + __( + '%1$sBzip2 compression and decompression%2$s requires functions (%3$s) which ' + . 'are unavailable on this system.' + ), + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]', + '[/a]', + $functions + ) + ) + ); + } + } + + /** + * Check GZipDump configuration + * + * @return void + */ + protected function performConfigChecksServerGZipdump() + { + // + // $cfg['GZipDump'] + // requires zlib functions + // + if ($this->cfg->getValue('GZipDump') + && (! $this->functionExists('gzopen') || ! $this->functionExists('gzencode')) + ) { + SetupIndex::messagesSet( + 'error', + 'GZipDump', + Descriptions::get('GZipDump'), + Sanitize::sanitizeMessage(sprintf( + __( + '%1$sGZip compression and decompression%2$s requires functions (%3$s) which ' + . 'are unavailable on this system.' + ), + '[a@' . Url::getCommon(['page' => 'form', 'formset' => 'Features']) . '#tab_Import_export]', + '[/a]', + 'gzencode' + )) + ); + } + } + + /** + * Wrapper around function_exists to allow mock in test + * + * @param string $name Function name + * + * @return boolean + */ + protected function functionExists($name) + { + return function_exists($name); + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/SpecialSchemaLinks.php b/srcs/phpmyadmin/libraries/classes/Config/SpecialSchemaLinks.php new file mode 100644 index 0000000..e0af136 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/SpecialSchemaLinks.php @@ -0,0 +1,478 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Links configuration for MySQL system tables + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Util; + +/** + * Class SpecialSchemaLinks + * @package PhpMyAdmin\Config + */ +class SpecialSchemaLinks +{ + /** + * This array represent the details for generating links inside + * special schemas like mysql, information_schema etc. + * Major element represent a schema. + * All the strings in this array represented in lower case + * + * Array structure ex: + * array( + * // Database name is the major element + * 'mysql' => array( + * // Table name + * 'db' => array( + * // Column name + * 'user' => array( + * // Main url param (can be an array where represent sql) + * 'link_param' => 'username', + * // Other url params + * 'link_dependancy_params' => array( + * 0 => array( + * // URL parameter name + * // (can be array where url param has static value) + * 'param_info' => 'hostname', + * // Column name related to url param + * 'column_name' => 'host' + * ) + * ), + * // Page to link + * 'default_page' => './server_privileges.php' + * ) + * ) + * ) + * ); + * + * @return array + */ + public static function get(): array + { + global $cfg; + + $defaultPage = './' . Util::getScriptNameForOption( + $cfg['DefaultTabTable'], + 'table' + ); + + return [ + 'mysql' => [ + 'columns_priv' => [ + 'user' => [ + 'link_param' => 'username', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'hostname', + 'column_name' => 'host', + ], + ], + 'default_page' => './server_privileges.php', + ], + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'Db', + ], + ], + 'default_page' => $defaultPage, + ], + 'column_name' => [ + 'link_param' => 'field', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'Db', + ], + 1 => [ + 'param_info' => 'table', + 'column_name' => 'Table_name', + ], + ], + 'default_page' => './tbl_structure.php?change_column=1', + ], + ], + 'db' => [ + 'user' => [ + 'link_param' => 'username', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'hostname', + 'column_name' => 'host', + ], + ], + 'default_page' => './server_privileges.php', + ], + ], + 'event' => [ + 'name' => [ + 'link_param' => 'item_name', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'db', + ], + ], + 'default_page' => './db_events.php?edit_item=1', + ], + + ], + 'innodb_index_stats' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'database_name', + ], + ], + 'default_page' => $defaultPage, + ], + 'index_name' => [ + 'link_param' => 'index', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'database_name', + ], + 1 => [ + 'param_info' => 'table', + 'column_name' => 'table_name', + ], + ], + 'default_page' => './tbl_structure.php', + ], + ], + 'innodb_table_stats' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'database_name', + ], + ], + 'default_page' => $defaultPage, + ], + ], + 'proc' => [ + 'name' => [ + 'link_param' => 'item_name', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'db', + ], + 1 => [ + 'param_info' => 'item_type', + 'column_name' => 'type', + ], + ], + 'default_page' => './db_routines.php?edit_item=1', + ], + 'specific_name' => [ + 'link_param' => 'item_name', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'db', + ], + 1 => [ + 'param_info' => 'item_type', + 'column_name' => 'type', + ], + ], + 'default_page' => './db_routines.php?edit_item=1', + ], + ], + 'proc_priv' => [ + 'user' => [ + 'link_param' => 'username', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'hostname', + 'column_name' => 'Host', + ], + ], + 'default_page' => './server_privileges.php', + ], + 'routine_name' => [ + 'link_param' => 'item_name', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'Db', + ], + 1 => [ + 'param_info' => 'item_type', + 'column_name' => 'Routine_type', + ], + ], + 'default_page' => './db_routines.php?edit_item=1', + ], + ], + 'proxies_priv' => [ + 'user' => [ + 'link_param' => 'username', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'hostname', + 'column_name' => 'Host', + ], + ], + 'default_page' => './server_privileges.php', + ], + ], + 'tables_priv' => [ + 'user' => [ + 'link_param' => 'username', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'hostname', + 'column_name' => 'Host', + ], + ], + 'default_page' => './server_privileges.php', + ], + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'Db', + ], + ], + 'default_page' => $defaultPage, + ], + ], + 'user' => [ + 'user' => [ + 'link_param' => 'username', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'hostname', + 'column_name' => 'host', + ], + ], + 'default_page' => './server_privileges.php', + ], + ], + ], + 'information_schema' => [ + 'columns' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'table_schema', + ], + ], + 'default_page' => $defaultPage, + ], + 'column_name' => [ + 'link_param' => 'field', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'table_schema', + ], + 1 => [ + 'param_info' => 'table', + 'column_name' => 'table_name', + ], + ], + 'default_page' => './tbl_structure.php?change_column=1', + ], + ], + 'key_column_usage' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'constraint_schema', + ], + ], + 'default_page' => $defaultPage, + ], + 'column_name' => [ + 'link_param' => 'field', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'table_schema', + ], + 1 => [ + 'param_info' => 'table', + 'column_name' => 'table_name', + ], + ], + 'default_page' => './tbl_structure.php?change_column=1', + ], + 'referenced_table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'referenced_table_schema', + ], + ], + 'default_page' => $defaultPage, + ], + 'referenced_column_name' => [ + 'link_param' => 'field', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'referenced_table_schema', + ], + 1 => [ + 'param_info' => 'table', + 'column_name' => 'referenced_table_name', + ], + ], + 'default_page' => './tbl_structure.php?change_column=1', + ], + ], + 'partitions' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'table_schema', + ], + ], + 'default_page' => $defaultPage, + ], + ], + 'processlist' => [ + 'user' => [ + 'link_param' => 'username', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'hostname', + 'column_name' => 'host', + ], + ], + 'default_page' => './server_privileges.php', + ], + ], + 'referential_constraints' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'constraint_schema', + ], + ], + 'default_page' => $defaultPage, + ], + 'referenced_table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'constraint_schema', + ], + ], + 'default_page' => $defaultPage, + ], + ], + 'routines' => [ + 'routine_name' => [ + 'link_param' => 'item_name', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'routine_schema', + ], + 1 => [ + 'param_info' => 'item_type', + 'column_name' => 'routine_type', + ], + ], + 'default_page' => './db_routines.php', + ], + ], + 'schemata' => [ + 'schema_name' => [ + 'link_param' => 'db', + 'default_page' => $defaultPage, + ], + ], + 'statistics' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'table_schema', + ], + ], + 'default_page' => $defaultPage, + ], + 'column_name' => [ + 'link_param' => 'field', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'table_schema', + ], + 1 => [ + 'param_info' => 'table', + 'column_name' => 'table_name', + ], + ], + 'default_page' => './tbl_structure.php?change_column=1', + ], + ], + 'tables' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'table_schema', + ], + ], + 'default_page' => $defaultPage, + ], + ], + 'table_constraints' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'table_schema', + ], + ], + 'default_page' => $defaultPage, + ], + ], + 'views' => [ + 'table_name' => [ + 'link_param' => 'table', + 'link_dependancy_params' => [ + 0 => [ + 'param_info' => 'db', + 'column_name' => 'table_schema', + ], + ], + 'default_page' => $defaultPage, + ], + ], + ], + ]; + } +} diff --git a/srcs/phpmyadmin/libraries/classes/Config/Validator.php b/srcs/phpmyadmin/libraries/classes/Config/Validator.php new file mode 100644 index 0000000..8294a90 --- /dev/null +++ b/srcs/phpmyadmin/libraries/classes/Config/Validator.php @@ -0,0 +1,594 @@ +<?php +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Form validation for configuration editor + * + * @package PhpMyAdmin + */ +declare(strict_types=1); + +namespace PhpMyAdmin\Config; + +use PhpMyAdmin\Config\ConfigFile; +use PhpMyAdmin\Core; +use PhpMyAdmin\DatabaseInterface; +use PhpMyAdmin\Util; +use function mysql_close; +use function mysql_connect; +use function mysqli_close; +use function mysqli_connect; + +/** + * Validation class for various validation functions + * + * Validation function takes two argument: id for which it is called + * and array of fields' values (usually values for entire formset, as defined + * in forms.inc.php). + * The function must always return an array with an error (or error array) + * assigned to a form element (formset name or field path). Even if there are + * no errors, key must be set with an empty value. + * + * Validation functions are assigned in $cfg_db['_validators'] (config.values.php). + * + * @package PhpMyAdmin + */ +class Validator +{ + /** + * Returns validator list + * + * @param ConfigFile $cf Config file instance + * + * @return array + */ + public static function getValidators(ConfigFile $cf) + { + static $validators = null; + + if ($validators !== null) { + return $validators; + } + + $validators = $cf->getDbEntry('_validators', []); + if ($GLOBALS['PMA_Config']->get('is_setup')) { + return $validators; + } + + // not in setup script: load additional validators for user + // preferences we need original config values not overwritten + // by user preferences, creating a new PhpMyAdmin\Config instance is a + // better idea than hacking into its code + $uvs = $cf->getDbEntry('_userValidators', []); + foreach ($uvs as $field => $uvList) { + $uvList = (array) $uvList; + foreach ($uvList as &$uv) { + if (! is_array($uv)) { + continue; + } + for ($i = 1, $nb = count($uv); $i < $nb; $i++) { + if (mb_substr($uv[$i], 0, 6) == 'value:') { + $uv[$i] = Core::arrayRead( + mb_substr($uv[$i], 6), + $GLOBALS['PMA_Config']->base_settings + ); + } + } + } + $validators[$field] = isset($validators[$field]) + ? array_merge((array) $validators[$field], $uvList) + : $uvList; + } + return $validators; + } + + /** + * Runs validation $validator_id on values $values and returns error list. + * + * Return values: + * o array, keys - field path or formset id, values - array of errors + * when $isPostSource is true values is an empty array to allow for error list + * cleanup in HTML document + * o false - when no validators match name(s) given by $validator_id + * + * @param ConfigFile $cf Config file instance + * @param string|array $validatorId ID of validator(s) to run + * @param array $values Values to validate + * @param bool $isPostSource tells whether $values are directly from + * POST request + * + * @return bool|array + */ + public static function validate( + ConfigFile $cf, + $validatorId, + array &$values, + $isPostSource + ) { + // find validators + $validatorId = (array) $validatorId; + $validators = static::getValidators($cf); + $vids = []; + foreach ($validatorId as &$vid) { + $vid = $cf->getCanonicalPath($vid); + if (isset($validators[$vid])) { + $vids[] = $vid; + } + } + if (empty($vids)) { + return false; + } + + // create argument list with canonical paths and remember path mapping + $arguments = []; + $keyMap = []; + foreach ($values as $k => $v) { + $k2 = $isPostSource ? str_replace('-', '/', $k) : $k; + $k2 = mb_strpos($k2, '/') + ? $cf->getCanonicalPath($k2) + : $k2; + $keyMap[$k2] = $k; + $arguments[$k2] = $v; + } + + // validate + $result = []; + foreach ($vids as $vid) { + // call appropriate validation functions + foreach ((array) $validators[$vid] as $validator) { + $vdef = (array) $validator; + $vname = array_shift($vdef); + $vname = 'PhpMyAdmin\Config\Validator::' . $vname; + $args = array_merge([$vid, &$arguments], $vdef); + $r = call_user_func_array($vname, $args); + + // merge results + if (! is_array($r)) { + continue; + } + + foreach ($r as $key => $errorList) { + // skip empty values if $isPostSource is false + if (! $isPostSource && empty($errorList)) { + continue; + } + if (! isset($result[$key])) { + $result[$key] = []; + } + $result[$key] = array_merge( + $result[$key], + (array) $errorList + ); + } + } + } + + // restore original paths + $newResult = []; + foreach ($result as $k => $v) { + $k2 = isset($keyMap[$k]) ? $keyMap[$k] : $k; + if (is_array($v)) { + $newResult[$k2] = array_map('htmlspecialchars', $v); + } else { + $newResult[$k2] = htmlspecialchars($v); + } + } + return empty($newResult) ? true : $newResult; + } + + /** + * Test database connection + * + * @param string $host host name + * @param string $port tcp port to use + * @param string $socket socket to use + * @param string $user username to use + * @param string $pass password to use + * @param string $errorKey key to use in return array + * + * @return bool|array + */ + public static function testDBConnection( + $host, + $port, + $socket, + $user, + $pass = null, + $errorKey = 'Server' + ) { + if ($GLOBALS['cfg']['DBG']['demo']) { + // Connection test disabled on the demo server! + return true; + } + + $error = null; + $host = Core::sanitizeMySQLHost($host); + + error_clear_last(); + + if (DatabaseInterface::checkDbExtension('mysqli')) { + $socket = empty($socket) ? null : $socket; + $port = empty($port) ? null : $port; + $extension = 'mysqli'; + } else { + $socket = empty($socket) ? null : ':' . ($socket[0] == '/' ? '' : '/') . $socket; + $port = empty($port) ? null : ':' . $port; + $extension = 'mysql'; + } + + if ($extension == 'mysql') { + $conn = @mysql_connect($host . $port . $socket, $user, $pass); + if (! $conn) { + $error = __('Could not connect to the database server!'); + } else { + mysql_close($conn); + } + } else { + $conn = @mysqli_connect($host, $user, $pass, null, $port, $socket); + if (! $conn) { + $error = __('Could not connect to the database server!'); + } else { + mysqli_close($conn); + } + } + if ($error !== null) { + $lastError = error_get_last(); + if ($lastError !== null) { + $error .= ' - ' . $lastError['message']; + } + } + return $error === null ? true : [$errorKey => $error]; + } + + /** + * Validate server config + * + * @param string $path path to config, not used + * keep this parameter since the method is invoked using + * reflection along with other similar methods + * @param array $values config values + * + * @return array + */ + public static function validateServer($path, array $values) + { + $result = [ + 'Server' => '', + 'Servers/1/user' => '', + 'Servers/1/SignonSession' => '', + 'Servers/1/SignonURL' => '', + ]; + $error = false; + if (empty($values['Servers/1/auth_type'])) { + $values['Servers/1/auth_type'] = ''; + $result['Servers/1/auth_type'] = __('Invalid authentication type!'); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'config' + && empty($values['Servers/1/user']) + ) { + $result['Servers/1/user'] = __( + 'Empty username while using [kbd]config[/kbd] authentication method!' + ); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'signon' + && empty($values['Servers/1/SignonSession']) + ) { + $result['Servers/1/SignonSession'] = __( + 'Empty signon session name ' + . 'while using [kbd]signon[/kbd] authentication method!' + ); + $error = true; + } + if ($values['Servers/1/auth_type'] == 'signon' + && empty($values['Servers/1/SignonURL']) + ) { + $result['Servers/1/SignonURL'] = __( + 'Empty signon URL while using [kbd]signon[/kbd] authentication ' + . 'method!' + ); + $error = true; + } + + if (! $error && $values['Servers/1/auth_type'] == 'config') { + $password = ''; + if (! empty($values['Servers/1/password'])) { + $password = $values['Servers/1/password']; + } + $test = static::testDBConnection( + empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'], + empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'], + empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'], + empty($values['Servers/1/user']) ? '' : $values['Servers/1/user'], + $password, + 'Server' + ); + + if ($test !== true) { + $result = array_merge($result, $test); + } + } + return $result; + } + + /** + * Validate pmadb config + * + * @param string $path path to config, not used + * keep this parameter since the method is invoked using + * reflection along with other similar methods + * @param array $values config values + * + * @return array + */ + public static function validatePMAStorage($path, array $values) + { + $result = [ + 'Server_pmadb' => '', + 'Servers/1/controluser' => '', + 'Servers/1/controlpass' => '', + ]; + $error = false; + + if (empty($values['Servers/1/pmadb'])) { + return $result; + } + + $result = []; + if (empty($values['Servers/1/controluser'])) { + $result['Servers/1/controluser'] = __( + 'Empty phpMyAdmin control user while using phpMyAdmin configuration ' + . 'storage!' + ); + $error = true; + } + if (empty($values['Servers/1/controlpass'])) { + $result['Servers/1/controlpass'] = __( + 'Empty phpMyAdmin control user password while using phpMyAdmin ' + . 'configuration storage!' + ); + $error = true; + } + if (! $error) { + $test = static::testDBConnection( + empty($values['Servers/1/host']) ? '' : $values['Servers/1/host'], + empty($values['Servers/1/port']) ? '' : $values['Servers/1/port'], + empty($values['Servers/1/socket']) ? '' : $values['Servers/1/socket'], + empty($values['Servers/1/controluser']) ? '' : $values['Servers/1/controluser'], + empty($values['Servers/1/controlpass']) ? '' : $values['Servers/1/controlpass'], + 'Server_pmadb' + ); + if ($test !== true) { + $result = array_merge($result, $test); + } + } + return $result; + } + + + /** + * Validates regular expression + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateRegex($path, array $values) + { + $result = [$path => '']; + + if (empty($values[$path])) { + return $result; + } + + error_clear_last(); + + $matches = []; + // in libraries/ListDatabase.php _checkHideDatabase(), + // a '/' is used as the delimiter for hide_db + @preg_match('/' . Util::requestString($values[$path]) . '/', '', $matches); + + $currentError = error_get_last(); + + if ($currentError !== null) { + $error = preg_replace('/^preg_match\(\): /', '', $currentError['message']); + return [$path => $error]; + } + + return $result; + } + + /** + * Validates TrustedProxies field + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateTrustedProxies($path, array $values) + { + $result = [$path => []]; + + if (empty($values[$path])) { + return $result; + } + + if (is_array($values[$path]) || is_object($values[$path])) { + // value already processed by FormDisplay::save + $lines = []; + foreach ($values[$path] as $ip => $v) { + $v = Util::requestString($v); + $lines[] = preg_match('/^-\d+$/', $ip) + ? $v + : $ip . ': ' . $v; + } + } else { + // AJAX validation + $lines = explode("\n", $values[$path]); + } + foreach ($lines as $line) { + $line = trim($line); + $matches = []; + // we catch anything that may (or may not) be an IP + if (! preg_match("/^(.+):(?:[ ]?)\\w+$/", $line, $matches)) { + $result[$path][] = __('Incorrect value:') . ' ' + . htmlspecialchars($line); + continue; + } + // now let's check whether we really have an IP address + if (filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false + && filter_var($matches[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false + ) { + $ip = htmlspecialchars(trim($matches[1])); + $result[$path][] = sprintf(__('Incorrect IP address: %s'), $ip); + continue; + } + } + + return $result; + } + + /** + * Tests integer value + * + * @param string $path path to config + * @param array $values config values + * @param bool $allowNegative allow negative values + * @param bool $allowZero allow zero + * @param int $maxValue max allowed value + * @param string $errorString error message string + * + * @return string empty string if test is successful + */ + public static function validateNumber( + $path, + array $values, + $allowNegative, + $allowZero, + $maxValue, + $errorString + ) { + if (empty($values[$path])) { + return ''; + } + + $value = Util::requestString($values[$path]); + + if (intval($value) != $value + || (! $allowNegative && $value < 0) + || (! $allowZero && $value == 0) + || $value > $maxValue + ) { + return $errorString; + } + + return ''; + } + + /** + * Validates port number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validatePortNumber($path, array $values) + { + return [ + $path => static::validateNumber( + $path, + $values, + false, + false, + 65535, + __('Not a valid port number!') + ), + ]; + } + + /** + * Validates positive number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validatePositiveNumber($path, array $values) + { + return [ + $path => static::validateNumber( + $path, + $values, + false, + false, + PHP_INT_MAX, + __('Not a positive number!') + ), + ]; + } + + /** + * Validates non-negative number + * + * @param string $path path to config + * @param array $values config values + * + * @return array + */ + public static function validateNonNegativeNumber($path, array $values) + { + return [ + $path => static::validateNumber( + $path, + $values, + false, + true, + PHP_INT_MAX, + __('Not a non-negative number!') + ), + ]; + } + + /** + * Validates value according to given regular expression + * Pattern and modifiers must be a valid for PCRE <b>and</b> JavaScript RegExp + * + * @param string $path path to config + * @param array $values config values + * @param string $regex regular expression to match + * + * @return array|string + */ + public static function validateByRegex($path, array $values, $regex) + { + if (! isset($values[$path])) { + return ''; + } + $result = preg_match($regex, Util::requestString($values[$path])); + return [$path => $result ? '' : __('Incorrect value!')]; + } + + /** + * Validates upper bound for numeric inputs + * + * @param string $path path to config + * @param array $values config values + * @param int $maxValue maximal allowed value + * + * @return array + */ + public static function validateUpperBound($path, array $values, $maxValue) + { + $result = $values[$path] <= $maxValue; + return [ + $path => $result ? '' : sprintf( + __('Value must be less than or equal to %s!'), + $maxValue + ), + ]; + } +} |
