Update website

This commit is contained in:
Guilhem Lavaux 2025-03-24 09:27:39 +01:00
parent a0b0d3dae7
commit ae7ef6ad45
3151 changed files with 566766 additions and 48 deletions

View file

@ -0,0 +1,577 @@
<?php
/**
* Config file management
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config;
use PhpMyAdmin\Core;
use function array_diff;
use function array_flip;
use function array_keys;
use function array_walk;
use function count;
use function is_array;
use function preg_replace;
/**
* Config file management class.
* Stores its data in $_SESSION
*/
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;
/**
* @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])) {
return;
}
$_SESSION[$this->id] = [];
}
/**
* Sets names of config options which will be placed in config file even if
* they are set to their default values (use only full paths)
*
* @param 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);
}
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,
function ($value, $key, $prefix) {
$this->flattenArray($value, $key, $prefix);
},
$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,
function ($value, $key, $prefix) {
$this->flattenArray($value, $key, $prefix);
},
''
);
$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,
function ($value, $key, $prefix) {
$this->flattenArray($value, $key, $prefix);
},
''
);
$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 $_SESSION[$this->id]['Servers'] ?? null;
}
/**
* Returns DSN of given server
*
* @param int $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
) {
return;
}
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) {
continue;
}
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],
function ($value, $key, $prefix) {
$this->flattenArray($value, $key, $prefix);
},
''
);
$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;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,310 @@
<?php
/**
* Form handling code.
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config;
use const E_USER_ERROR;
use function array_combine;
use function array_shift;
use function array_walk;
use function count;
use function gettype;
use function is_array;
use function is_bool;
use function is_int;
use function is_string;
use function ltrim;
use function mb_strpos;
use function mb_strrpos;
use function mb_substr;
use function str_replace;
use function trigger_error;
/**
* Base class for forms, loads default configuration options, checks allowed
* values etc.
*/
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;
/**
* A counter for the number of groups
*
* @var int
*/
private static $groupCounter = 0;
/**
* 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 $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)
{
if (is_array($value)) {
$prefix .= $key . '/';
array_walk(
$value,
function ($value, $key, $prefix) {
$this->readFormPathsCallback($value, $key, $prefix);
},
$prefix
);
return;
}
if (! is_int($key)) {
$this->default[$prefix . $key] = $value;
$value = $key;
}
// add unique id to group ends
if ($value === ':group:end') {
$value .= ':' . self::$groupCounter++;
}
$this->fields[] = $prefix . $value;
}
/**
* Reset the group counter, function for testing purposes
*/
public static function resetGroupCounter(): void
{
self::$groupCounter = 0;
}
/**
* 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,
function ($value, $key, $prefix) {
$this->readFormPathsCallback($value, $key, $prefix);
},
''
);
// $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;
}
}
/**
* Remove slashes from group names
*
* @see issue #15836
*
* @param array $form The form data
*
* @return array
*/
protected function cleanGroupPaths(array $form): array
{
foreach ($form as &$name) {
if (! is_string($name)) {
continue;
}
if (mb_strpos($name, ':group:') !== 0) {
continue;
}
$name = str_replace('/', '-', $name);
}
return $form;
}
/**
* 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;
$form = $this->cleanGroupPaths($form);
$this->readFormPaths($form);
$this->readTypes();
}
}

View file

@ -0,0 +1,967 @@
<?php
/**
* 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
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config;
use PhpMyAdmin\Config\Forms\User\UserFormList;
use PhpMyAdmin\Html\MySQLDocumentation;
use PhpMyAdmin\Sanitize;
use PhpMyAdmin\Util;
use const E_USER_WARNING;
use function array_flip;
use function array_keys;
use function array_search;
use function count;
use function explode;
use function function_exists;
use function gettype;
use function implode;
use function is_array;
use function is_bool;
use function is_numeric;
use function mb_substr;
use function preg_match;
use function settype;
use function sprintf;
use function str_replace;
use function trigger_error;
use function trim;
/**
* Form management class, displays and processes forms
*/
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;
/**
* @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 bool 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 = $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])) {
continue;
}
$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) . '})';
return $htmlOutput . $this->formDisplayTemplate->displayJavascript($js);
}
/**
* 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)) {
continue;
}
$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':
$val = $valueDefault;
if (isset($val['wrapper_params'])) {
unset($val['wrapper_params']);
}
$jsLine .= '\'' . Sanitize::escapeJsString(implode("\n", $val))
. '\'';
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|bool $value Current value
* @param array $allowed List of allowed values
*/
private function validateSelect(&$value, array $allowed): bool
{
$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)
) {
continue;
}
// keep boolean value as boolean
if (! is_bool($value)) {
// phpcs:ignore Generic.PHP.ForbiddenFunctions
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 bool 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) {
if (! isset($this->forms[$formName])) {
continue;
}
/** @var Form $form */
$form = $this->forms[$formName];
// 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 = (string) $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') {
$this->errors[$form->name][] = sprintf(
__('Missing data for %s'),
'<i>' . Descriptions::get($systemPath) . '</i>'
);
$result = false;
continue;
}
$_POST[$key] = false;
}
// user preferences allow/disallow
if ($isSetupScript
&& isset($this->userprefsKeys[$systemPath])
) {
if (isset($this->userprefsDisallow[$systemPath], $_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]);
// phpcs:ignore Generic.PHP.ForbiddenFunctions
settype($_POST[$key], 'float');
break;
case 'boolean':
case 'integer':
if ($_POST[$key] !== '') {
$_POST[$key] = Util::requestString($_POST[$key]);
// phpcs:ignore Generic.PHP.ForbiddenFunctions
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 bool
*/
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 MySQLDocumentation::getDocumentationLink(
'config',
'cfg_' . $this->getOptName($path),
Sanitize::isSetup() ? '../' : './'
);
}
/**
* 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')) {
return;
}
if ($systemPath !== 'MaxDbList' && $systemPath !== 'MaxTableList'
&& $systemPath !== 'QueryHistoryMax'
) {
return;
}
$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 === '') {
continue;
}
$_POST[$key][] = $v;
}
}
}

View file

@ -0,0 +1,491 @@
<?php
/**
* Form templates
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config;
use PhpMyAdmin\Config;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Sanitize;
use PhpMyAdmin\Template;
use function array_flip;
use function array_merge;
use function array_shift;
use function defined;
use function htmlspecialchars;
use function htmlspecialchars_decode;
use function implode;
use function is_bool;
use function mb_strtolower;
use function sprintf;
use function is_string;
/**
* PhpMyAdmin\Config\FormDisplayTemplate class
*/
class FormDisplayTemplate
{
/** @var int */
public $group;
/** @var Config */
protected $config;
/** @var Template */
public $template;
/**
* @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)
*/
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';
}
/**
* 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;
}
return $this->template->render('config/form_display/form_top', [
'method' => $method,
'action' => $action,
'has_check_page_refresh' => $hasCheckPageRefresh,
'hidden_fields' => (array) $hiddenFields,
]);
}
/**
* 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
*/
public function displayTabsTop(array $tabs): string
{
return $this->template->render('config/form_display/tabs_top', ['tabs' => $tabs]);
}
/**
* 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
*/
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
*/
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] = Generator::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="w-75" ' . $nameId . $fieldClass
. ' value="' . htmlspecialchars($value) . '">';
break;
case 'password':
$htmlOutput .= '<input type="password" class="w-75" ' . $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_string($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="w-75" ' . $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':
$val = $value;
if (isset($val['wrapper_params'])) {
unset($val['wrapper_params']);
}
$htmlOutput .= '<textarea cols="35" rows="5" ' . $nameId . $fieldClass
. '>' . htmlspecialchars(implode("\n", $val)) . '</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>&nbsp;</td>';
}
$htmlOutput .= '</tr>';
return $htmlOutput;
}
/**
* Display group header
*
* @param string $headerText Text of header
*/
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
*/
public function displayGroupFooter(): void
{
$this->group--;
}
/**
* Displays bottom part of a fieldset
*
* @param bool $showButtons Whether show submit and reset button
*/
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
*/
public function displayTabsBottom(): string
{
return $this->template->render('config/form_display/tabs_bottom');
}
/**
* Displays bottom part of the form
*/
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
*/
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
*/
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,
]);
}
}

View file

@ -0,0 +1,85 @@
<?php
/**
* Base class for preferences.
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms;
use PhpMyAdmin\Config\ConfigFile;
use PhpMyAdmin\Config\FormDisplay;
use function is_int;
/**
* Base form for user preferences
*/
abstract class BaseForm extends FormDisplay
{
/**
* @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'
*
* @return array
*
* @todo This should be abstract, but that does not work in PHP 5
*/
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
*
* @return string
*
* @todo This should be abstract, but that does not work in PHP 5
*/
public static function getName()
{
return '';
}
}

View file

@ -0,0 +1,149 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms;
use PhpMyAdmin\Config\ConfigFile;
use function array_merge;
use function in_array;
class BaseFormList
{
/**
* List of all forms
*
* @var array
*/
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 string|null
*/
public static function get($name)
{
if (static::isValid($name)) {
return static::$ns . $name . 'Form';
}
return null;
}
/**
* @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 bool 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 bool
*/
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;
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Page;
use PhpMyAdmin\Config\Forms\BaseForm;
use PhpMyAdmin\Config\Forms\User\MainForm;
class BrowseForm extends BaseForm
{
/**
* @return array
*/
public static function getForms()
{
return [
'Browse' => MainForm::getForms()['Browse'],
];
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Page;
use PhpMyAdmin\Config\Forms\BaseForm;
use PhpMyAdmin\Config\Forms\User\MainForm;
class DbStructureForm extends BaseForm
{
/**
* @return array
*/
public static function getForms()
{
return [
'DbStructure' => MainForm::getForms()['DbStructure'],
];
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* User preferences form
*/
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 extends BaseForm
{
/**
* @return array
*/
public static function getForms()
{
return [
'Edit' => MainForm::getForms()['Edit'],
'Text_fields' => FeaturesForm::getForms()['Text_fields'],
];
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Page;
class ExportForm extends \PhpMyAdmin\Config\Forms\User\ExportForm
{
}

View file

@ -0,0 +1,12 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Page;
class ImportForm extends \PhpMyAdmin\Config\Forms\User\ImportForm
{
}

View file

@ -0,0 +1,12 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Page;
class NaviForm extends \PhpMyAdmin\Config\Forms\User\NaviForm
{
}

View file

@ -0,0 +1,27 @@
<?php
/**
* Page preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Page;
use PhpMyAdmin\Config\Forms\BaseFormList;
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\\';
}

View file

@ -0,0 +1,12 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Page;
class SqlForm extends \PhpMyAdmin\Config\Forms\User\SqlForm
{
}

View file

@ -0,0 +1,24 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Page;
use PhpMyAdmin\Config\Forms\BaseForm;
use PhpMyAdmin\Config\Forms\User\MainForm;
class TableStructureForm extends BaseForm
{
/**
* @return array
*/
public static function getForms()
{
return [
'TableStructure' => MainForm::getForms()['TableStructure'],
];
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Setup;
use PhpMyAdmin\Config\Forms\BaseForm;
class ConfigForm extends BaseForm
{
/**
* @return array
*/
public static function getForms()
{
return [
'Config' => [
'DefaultLang',
'ServerDefault',
],
];
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Setup;
class ExportForm extends \PhpMyAdmin\Config\Forms\User\ExportForm
{
}

View file

@ -0,0 +1,76 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Setup;
use function array_diff;
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',
'CaptchaSiteVerifyURL',
];
$result['Developer'] = [
'UserprefsDeveloperTab',
'DBG/sql',
];
$result['Other_core_settings'] = [
'OBGzip',
'PersistentConnections',
'ExecTimeLimit',
'MemoryLimit',
'UseDbSearch',
'ProxyUrl',
'ProxyUser',
'ProxyPass',
'AllowThirdPartyFraming',
'ZeroConf',
];
return $result;
// phpcs:enable
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Setup;
class ImportForm extends \PhpMyAdmin\Config\Forms\User\ImportForm
{
}

View file

@ -0,0 +1,24 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace 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;
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Setup;
class NaviForm extends \PhpMyAdmin\Config\Forms\User\NaviForm
{
}

View file

@ -0,0 +1,111 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Setup;
use PhpMyAdmin\Config\Forms\BaseForm;
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
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* Setup preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\Setup;
use PhpMyAdmin\Config\Forms\BaseFormList;
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\\';
}

View file

@ -0,0 +1,23 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace 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;
}
}

View file

@ -0,0 +1,153 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\User;
use PhpMyAdmin\Config\Forms\BaseForm;
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');
}
}

View file

@ -0,0 +1,90 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\User;
use PhpMyAdmin\Config\Forms\BaseForm;
class FeaturesForm extends BaseForm
{
/**
* @return array
*/
public static function getForms()
{
$result = [
'General' => [
'VersionCheck',
'NaturalOrder',
'InitialSlidersState',
'LoginCookieValidity',
'SkipLockedTables',
'DisableMultiTableMaintenance',
'ShowHint',
'SendErrorReports',
'ConsoleEnterExecutes',
'DisableShortcutKeys',
'FirstDayOfCalendar',
],
'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');
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\User;
use PhpMyAdmin\Config\Forms\BaseForm;
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');
}
}

View file

@ -0,0 +1,90 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\User;
use PhpMyAdmin\Config\Forms\BaseForm;
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');
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\User;
use PhpMyAdmin\Config\Forms\BaseForm;
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');
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\User;
use PhpMyAdmin\Config\Forms\BaseForm;
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');
}
}

View file

@ -0,0 +1,25 @@
<?php
/**
* User preferences form
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config\Forms\User;
use PhpMyAdmin\Config\Forms\BaseFormList;
class UserFormList extends BaseFormList
{
/** @var array */
protected static $all = [
'Features',
'Sql',
'Navi',
'Main',
'Export',
'Import',
];
/** @var string */
protected static $ns = '\\PhpMyAdmin\\Config\\Forms\\User\\';
}

View file

@ -0,0 +1,199 @@
<?php
/**
* Page-related settings
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config;
use PhpMyAdmin\Config\Forms\Page\PageFormList;
use PhpMyAdmin\Core;
use PhpMyAdmin\Message;
use PhpMyAdmin\Response;
use PhpMyAdmin\UserPreferences;
/**
* Page-related settings
*/
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;
/**
* @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']->baseSettings);
$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()) {
return;
}
// save settings
$result = $this->userPreferences->save($cf->getConfigArray());
if ($result === true) {
// reload page
$response = Response::getInstance();
Core::sendHeaderLocation(
$response->getFooter()->getSelfUrl()
);
exit;
}
$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="alert alert-danger config-form" role="alert">'
. '<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;
}
}

View file

@ -0,0 +1,568 @@
<?php
/**
* Server config checks management
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config;
use PhpMyAdmin\Core;
use PhpMyAdmin\Sanitize;
use PhpMyAdmin\Setup\Index as SetupIndex;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
use function count;
use function function_exists;
use function htmlspecialchars;
use function implode;
use function ini_get;
use function preg_match;
use function sprintf;
use function strlen;
/**
* Performs various compatibility, security and consistency checks on current config
*
* Outputs results to message list, must be called between SetupIndex::messagesBegin()
* and SetupIndex::messagesEnd()
*/
class ServerConfigChecks
{
/** @var ConfigFile configurations being checked */
protected $cfg;
/**
* @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;
[$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 bool $cookieAuthUsed Cookie auth is used
* @param string $blowfishSecret Blowfish secret
* @param bool $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);
[$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')
) {
continue;
}
$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|null $blowfishSecret Blowfish secret
* @param bool $cookieAuthServer Cookie auth is used
* @param bool $blowfishSecretSet Blowfish secret set
*
* @return array
*/
protected function performConfigChecksServersSetBlowfishSecret(
$blowfishSecret,
$cookieAuthServer,
$blowfishSecretSet
): array {
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') {
return $serverName . ' [' . $serverId . ']';
}
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')) {
return;
}
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 bool $cookieAuthUsed Cookie auth is used
* @param bool $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) {
return;
}
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'))
) {
return;
}
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'))
) {
return;
}
$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'))
) {
return;
}
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 bool
*/
protected function functionExists($name)
{
return function_exists($name);
}
}

View file

@ -0,0 +1,489 @@
<?php
/**
* Links configuration for MySQL system tables
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
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' => './' . Url::getFromRoute('/server/privileges')
* )
* )
* )
* );
*
* @return array<string,array<string,array<string,array<string,array<int,array<string,string>>|string>>>>
*
* @phpstan-return array<
* string, array<
* string, array<
* string,
* array{
* 'link_param': string,
* 'link_dependancy_params'?: array<
* int,
* array{'param_info': string, 'column_name': string}
* >,
* 'default_page': string
* }>
* >
* >
* }
*/
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' => './' . Url::getFromRoute('/server/privileges'),
],
'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' => './' . Url::getFromRoute('/table/structure/change', ['change_column' => 1]),
],
],
'db' => [
'user' => [
'link_param' => 'username',
'link_dependancy_params' => [
0 => [
'param_info' => 'hostname',
'column_name' => 'host',
],
],
'default_page' => './' . Url::getFromRoute('/server/privileges'),
],
],
'event' => [
'name' => [
'link_param' => 'item_name',
'link_dependancy_params' => [
0 => [
'param_info' => 'db',
'column_name' => 'db',
],
],
'default_page' => './' . Url::getFromRoute('/database/events', ['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' => './' . Url::getFromRoute('/table/structure'),
],
],
'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' => './' . Url::getFromRoute('/database/routines', ['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' => './' . Url::getFromRoute('/database/routines', ['edit_item' => 1]),
],
],
'proc_priv' => [
'user' => [
'link_param' => 'username',
'link_dependancy_params' => [
0 => [
'param_info' => 'hostname',
'column_name' => 'Host',
],
],
'default_page' => './' . Url::getFromRoute('/server/privileges'),
],
'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' => './' . Url::getFromRoute('/database/routines', ['edit_item' => 1]),
],
],
'proxies_priv' => [
'user' => [
'link_param' => 'username',
'link_dependancy_params' => [
0 => [
'param_info' => 'hostname',
'column_name' => 'Host',
],
],
'default_page' => './' . Url::getFromRoute('/server/privileges'),
],
],
'tables_priv' => [
'user' => [
'link_param' => 'username',
'link_dependancy_params' => [
0 => [
'param_info' => 'hostname',
'column_name' => 'Host',
],
],
'default_page' => './' . Url::getFromRoute('/server/privileges'),
],
'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' => './' . Url::getFromRoute('/server/privileges'),
],
],
],
'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' => './' . Url::getFromRoute('/table/structure/change', ['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' => './' . Url::getFromRoute('/table/structure/change', ['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' => './' . Url::getFromRoute('/table/structure/change', ['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' => './' . Url::getFromRoute('/server/privileges'),
],
],
'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' => './' . Url::getFromRoute('/database/routines'),
],
],
'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' => './' . Url::getFromRoute('/table/structure/change', ['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,
],
],
],
];
}
}

View file

@ -0,0 +1,608 @@
<?php
/**
* Form validation for configuration editor
*/
declare(strict_types=1);
namespace PhpMyAdmin\Config;
use PhpMyAdmin\Core;
use PhpMyAdmin\Util;
use function mysqli_report;
use const FILTER_FLAG_IPV4;
use const FILTER_FLAG_IPV6;
use const FILTER_VALIDATE_IP;
use const MYSQLI_REPORT_OFF;
use const PHP_INT_MAX;
use function array_map;
use function array_merge;
use function array_shift;
use function call_user_func_array;
use function count;
use function error_clear_last;
use function error_get_last;
use function explode;
use function filter_var;
use function htmlspecialchars;
use function intval;
use function is_array;
use function is_object;
use function mb_strpos;
use function mb_substr;
use function mysqli_close;
use function mysqli_connect;
use function preg_match;
use function preg_replace;
use function sprintf;
use function str_replace;
use function trim;
/**
* 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).
* 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).
*/
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:') {
continue;
}
$uv[$i] = Core::arrayRead(
mb_substr($uv[$i], 6),
$GLOBALS['PMA_Config']->baseSettings
);
}
}
$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])) {
continue;
}
$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 = $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();
$socket = empty($socket) ? null : $socket;
$port = empty($port) ? null : $port;
mysqli_report(MYSQLI_REPORT_OFF);
$conn = @mysqli_connect($host, $user, (string) $pass, '', $port, (string) $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
),
];
}
}