PageRenderTime 34ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/classes/Config/ConfigFile.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 566 lines | 272 code | 72 blank | 222 comment | 33 complexity | 90c7d35f46e6e4d7c45ca00c2c8516ee MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
  1. <?php
  2. /**
  3. * Config file management
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Config;
  7. use PhpMyAdmin\Core;
  8. use function array_diff;
  9. use function array_flip;
  10. use function array_keys;
  11. use function array_walk;
  12. use function count;
  13. use function is_array;
  14. use function preg_replace;
  15. /**
  16. * Config file management class.
  17. * Stores its data in $_SESSION
  18. */
  19. class ConfigFile
  20. {
  21. /**
  22. * Stores default phpMyAdmin config
  23. *
  24. * @see Settings
  25. *
  26. * @var array
  27. */
  28. private $defaultCfg;
  29. /**
  30. * Stores allowed values for non-standard fields
  31. *
  32. * @var array
  33. */
  34. private $cfgDb;
  35. /**
  36. * Stores original PMA config, not modified by user preferences
  37. *
  38. * @var array|null
  39. */
  40. private $baseCfg;
  41. /**
  42. * Whether we are currently working in PMA Setup context
  43. *
  44. * @var bool
  45. */
  46. private $isInSetup;
  47. /**
  48. * Keys which will be always written to config file
  49. *
  50. * @var array
  51. */
  52. private $persistKeys = [];
  53. /**
  54. * Changes keys while updating config in {@link updateWithGlobalConfig()}
  55. * or reading by {@link getConfig()} or {@link getConfigArray()}
  56. *
  57. * @var array
  58. */
  59. private $cfgUpdateReadMapping = [];
  60. /**
  61. * Key filter for {@link set()}
  62. *
  63. * @var array|null
  64. */
  65. private $setFilter;
  66. /**
  67. * Instance id (key in $_SESSION array, separate for each server -
  68. * ConfigFile{server id})
  69. *
  70. * @var string
  71. */
  72. private $id;
  73. /**
  74. * Result for {@link flattenArray()}
  75. *
  76. * @var array|null
  77. */
  78. private $flattenArrayResult;
  79. /**
  80. * @param array|null $baseConfig base configuration read from
  81. * {@link PhpMyAdmin\Config::$base_config},
  82. * use only when not in PMA Setup
  83. */
  84. public function __construct($baseConfig = null)
  85. {
  86. // load default config values
  87. $settings = new Settings([]);
  88. $this->defaultCfg = $settings->toArray();
  89. // load additional config information
  90. $this->cfgDb = include ROOT_PATH . 'libraries/config.values.php';
  91. // apply default values overrides
  92. if (count($this->cfgDb['_overrides'])) {
  93. foreach ($this->cfgDb['_overrides'] as $path => $value) {
  94. Core::arrayWrite($path, $this->defaultCfg, $value);
  95. }
  96. }
  97. $this->baseCfg = $baseConfig;
  98. $this->isInSetup = $baseConfig === null;
  99. $this->id = 'ConfigFile' . $GLOBALS['server'];
  100. if (isset($_SESSION[$this->id])) {
  101. return;
  102. }
  103. $_SESSION[$this->id] = [];
  104. }
  105. /**
  106. * Sets names of config options which will be placed in config file even if
  107. * they are set to their default values (use only full paths)
  108. *
  109. * @param array $keys the names of the config options
  110. */
  111. public function setPersistKeys(array $keys): void
  112. {
  113. // checking key presence is much faster than searching so move values
  114. // to keys
  115. $this->persistKeys = array_flip($keys);
  116. }
  117. /**
  118. * Returns flipped array set by {@link setPersistKeys()}
  119. *
  120. * @return array
  121. */
  122. public function getPersistKeysMap()
  123. {
  124. return $this->persistKeys;
  125. }
  126. /**
  127. * By default ConfigFile allows setting of all configuration keys, use
  128. * this method to set up a filter on {@link set()} method
  129. *
  130. * @param array|null $keys array of allowed keys or null to remove filter
  131. */
  132. public function setAllowedKeys($keys): void
  133. {
  134. if ($keys === null) {
  135. $this->setFilter = null;
  136. return;
  137. }
  138. // checking key presence is much faster than searching so move values
  139. // to keys
  140. $this->setFilter = array_flip($keys);
  141. }
  142. /**
  143. * Sets path mapping for updating config in
  144. * {@link updateWithGlobalConfig()} or reading
  145. * by {@link getConfig()} or {@link getConfigArray()}
  146. *
  147. * @param array $mapping Contains the mapping of "Server/config options"
  148. * to "Server/1/config options"
  149. */
  150. public function setCfgUpdateReadMapping(array $mapping): void
  151. {
  152. $this->cfgUpdateReadMapping = $mapping;
  153. }
  154. /**
  155. * Resets configuration data
  156. */
  157. public function resetConfigData(): void
  158. {
  159. $_SESSION[$this->id] = [];
  160. }
  161. /**
  162. * Sets configuration data (overrides old data)
  163. *
  164. * @param array $cfg Configuration options
  165. */
  166. public function setConfigData(array $cfg): void
  167. {
  168. $_SESSION[$this->id] = $cfg;
  169. }
  170. /**
  171. * Sets config value
  172. *
  173. * @param string $path Path
  174. * @param mixed $value Value
  175. * @param string $canonicalPath Canonical path
  176. */
  177. public function set($path, $value, $canonicalPath = null): void
  178. {
  179. if ($canonicalPath === null) {
  180. $canonicalPath = $this->getCanonicalPath($path);
  181. }
  182. if ($this->setFilter !== null && ! isset($this->setFilter[$canonicalPath])) {
  183. return;
  184. }
  185. // if the path isn't protected it may be removed
  186. if (isset($this->persistKeys[$canonicalPath])) {
  187. Core::arrayWrite($path, $_SESSION[$this->id], $value);
  188. return;
  189. }
  190. $defaultValue = $this->getDefault($canonicalPath);
  191. $removePath = $value === $defaultValue;
  192. if ($this->isInSetup) {
  193. // remove if it has a default value or is empty
  194. $removePath = $removePath
  195. || (empty($value) && empty($defaultValue));
  196. } else {
  197. // get original config values not overwritten by user
  198. // preferences to allow for overwriting options set in
  199. // config.inc.php with default values
  200. $instanceDefaultValue = Core::arrayRead($canonicalPath, $this->baseCfg);
  201. // remove if it has a default value and base config (config.inc.php)
  202. // uses default value
  203. $removePath = $removePath
  204. && ($instanceDefaultValue === $defaultValue);
  205. }
  206. if ($removePath) {
  207. Core::arrayRemove($path, $_SESSION[$this->id]);
  208. return;
  209. }
  210. Core::arrayWrite($path, $_SESSION[$this->id], $value);
  211. }
  212. /**
  213. * Flattens multidimensional array, changes indices to paths
  214. * (eg. 'key/subkey').
  215. * Used as array_walk() callback.
  216. *
  217. * @param mixed $value Value
  218. * @param mixed $key Key
  219. * @param mixed $prefix Prefix
  220. */
  221. private function flattenArray($value, $key, $prefix): void
  222. {
  223. // no recursion for numeric arrays
  224. if (is_array($value) && ! isset($value[0])) {
  225. $prefix .= $key . '/';
  226. array_walk(
  227. $value,
  228. function ($value, $key, $prefix): void {
  229. $this->flattenArray($value, $key, $prefix);
  230. },
  231. $prefix
  232. );
  233. } else {
  234. $this->flattenArrayResult[$prefix . $key] = $value;
  235. }
  236. }
  237. /**
  238. * Returns default config in a flattened array
  239. *
  240. * @return array
  241. */
  242. public function getFlatDefaultConfig()
  243. {
  244. $this->flattenArrayResult = [];
  245. array_walk(
  246. $this->defaultCfg,
  247. function ($value, $key, $prefix): void {
  248. $this->flattenArray($value, $key, $prefix);
  249. },
  250. ''
  251. );
  252. $flatConfig = $this->flattenArrayResult;
  253. $this->flattenArrayResult = null;
  254. return $flatConfig;
  255. }
  256. /**
  257. * Updates config with values read from given array
  258. * (config will contain differences to defaults from {@see \PhpMyAdmin\Config\Settings}).
  259. *
  260. * @param array $cfg Configuration
  261. */
  262. public function updateWithGlobalConfig(array $cfg): void
  263. {
  264. // load config array and flatten it
  265. $this->flattenArrayResult = [];
  266. array_walk(
  267. $cfg,
  268. function ($value, $key, $prefix): void {
  269. $this->flattenArray($value, $key, $prefix);
  270. },
  271. ''
  272. );
  273. $flatConfig = $this->flattenArrayResult;
  274. $this->flattenArrayResult = null;
  275. // save values map for translating a few user preferences paths,
  276. // should be complemented by code reading from generated config
  277. // to perform inverse mapping
  278. foreach ($flatConfig as $path => $value) {
  279. if (isset($this->cfgUpdateReadMapping[$path])) {
  280. $path = $this->cfgUpdateReadMapping[$path];
  281. }
  282. $this->set($path, $value, $path);
  283. }
  284. }
  285. /**
  286. * Returns config value or $default if it's not set
  287. *
  288. * @param string $path Path of config file
  289. * @param mixed $default Default values
  290. *
  291. * @return mixed
  292. */
  293. public function get($path, $default = null)
  294. {
  295. return Core::arrayRead($path, $_SESSION[$this->id], $default);
  296. }
  297. /**
  298. * Returns default config value or $default it it's not set ie. it doesn't
  299. * exist in {@see \PhpMyAdmin\Config\Settings} ($cfg) and config.values.php
  300. * ($_cfg_db['_overrides'])
  301. *
  302. * @param string $canonicalPath Canonical path
  303. * @param mixed $default Default value
  304. *
  305. * @return mixed
  306. */
  307. public function getDefault($canonicalPath, $default = null)
  308. {
  309. return Core::arrayRead($canonicalPath, $this->defaultCfg, $default);
  310. }
  311. /**
  312. * Returns config value, if it's not set uses the default one; returns
  313. * $default if the path isn't set and doesn't contain a default value
  314. *
  315. * @param string $path Path
  316. * @param mixed $default Default value
  317. *
  318. * @return mixed
  319. */
  320. public function getValue($path, $default = null)
  321. {
  322. $v = Core::arrayRead($path, $_SESSION[$this->id], null);
  323. if ($v !== null) {
  324. return $v;
  325. }
  326. $path = $this->getCanonicalPath($path);
  327. return $this->getDefault($path, $default);
  328. }
  329. /**
  330. * Returns canonical path
  331. *
  332. * @param string $path Path
  333. *
  334. * @return string
  335. */
  336. public function getCanonicalPath($path)
  337. {
  338. return preg_replace('#^Servers/([\d]+)/#', 'Servers/1/', $path);
  339. }
  340. /**
  341. * Returns config database entry for $path
  342. *
  343. * @param string $path path of the variable in config db
  344. * @param mixed $default default value
  345. *
  346. * @return mixed
  347. */
  348. public function getDbEntry($path, $default = null)
  349. {
  350. return Core::arrayRead($path, $this->cfgDb, $default);
  351. }
  352. /**
  353. * Returns server count
  354. *
  355. * @return int
  356. */
  357. public function getServerCount()
  358. {
  359. return isset($_SESSION[$this->id]['Servers'])
  360. ? count($_SESSION[$this->id]['Servers'])
  361. : 0;
  362. }
  363. /**
  364. * Returns server list
  365. *
  366. * @return array
  367. */
  368. public function getServers(): array
  369. {
  370. return $_SESSION[$this->id]['Servers'] ?? [];
  371. }
  372. /**
  373. * Returns DSN of given server
  374. *
  375. * @param int $server server index
  376. *
  377. * @return string
  378. */
  379. public function getServerDSN($server)
  380. {
  381. if (! isset($_SESSION[$this->id]['Servers'][$server])) {
  382. return '';
  383. }
  384. $path = 'Servers/' . $server;
  385. $dsn = 'mysqli://';
  386. if ($this->getValue($path . '/auth_type') === 'config') {
  387. $dsn .= $this->getValue($path . '/user');
  388. if (! empty($this->getValue($path . '/password'))) {
  389. $dsn .= ':***';
  390. }
  391. $dsn .= '@';
  392. }
  393. if ($this->getValue($path . '/host') !== 'localhost') {
  394. $dsn .= $this->getValue($path . '/host');
  395. $port = $this->getValue($path . '/port');
  396. if ($port) {
  397. $dsn .= ':' . $port;
  398. }
  399. } else {
  400. $dsn .= $this->getValue($path . '/socket');
  401. }
  402. return $dsn;
  403. }
  404. /**
  405. * Returns server name
  406. *
  407. * @param int $id server index
  408. *
  409. * @return string
  410. */
  411. public function getServerName($id)
  412. {
  413. if (! isset($_SESSION[$this->id]['Servers'][$id])) {
  414. return '';
  415. }
  416. $verbose = $this->get('Servers/' . $id . '/verbose');
  417. if (! empty($verbose)) {
  418. return $verbose;
  419. }
  420. $host = $this->get('Servers/' . $id . '/host');
  421. return empty($host) ? 'localhost' : $host;
  422. }
  423. /**
  424. * Removes server
  425. *
  426. * @param int $server server index
  427. */
  428. public function removeServer($server): void
  429. {
  430. if (! isset($_SESSION[$this->id]['Servers'][$server])) {
  431. return;
  432. }
  433. $lastServer = $this->getServerCount();
  434. for ($i = $server; $i < $lastServer; $i++) {
  435. $_SESSION[$this->id]['Servers'][$i] = $_SESSION[$this->id]['Servers'][$i + 1];
  436. }
  437. unset($_SESSION[$this->id]['Servers'][$lastServer]);
  438. if (! isset($_SESSION[$this->id]['ServerDefault']) || $_SESSION[$this->id]['ServerDefault'] != $lastServer) {
  439. return;
  440. }
  441. unset($_SESSION[$this->id]['ServerDefault']);
  442. }
  443. /**
  444. * Returns configuration array (full, multidimensional format)
  445. *
  446. * @return array
  447. */
  448. public function getConfig()
  449. {
  450. $c = $_SESSION[$this->id];
  451. foreach ($this->cfgUpdateReadMapping as $mapTo => $mapFrom) {
  452. // if the key $c exists in $map_to
  453. if (Core::arrayRead($mapTo, $c) === null) {
  454. continue;
  455. }
  456. Core::arrayWrite($mapTo, $c, Core::arrayRead($mapFrom, $c));
  457. Core::arrayRemove($mapFrom, $c);
  458. }
  459. return $c;
  460. }
  461. /**
  462. * Returns configuration array (flat format)
  463. *
  464. * @return array
  465. */
  466. public function getConfigArray()
  467. {
  468. $this->flattenArrayResult = [];
  469. array_walk(
  470. $_SESSION[$this->id],
  471. function ($value, $key, $prefix): void {
  472. $this->flattenArray($value, $key, $prefix);
  473. },
  474. ''
  475. );
  476. $c = $this->flattenArrayResult;
  477. $this->flattenArrayResult = null;
  478. $persistKeys = array_diff(
  479. array_keys($this->persistKeys),
  480. array_keys($c)
  481. );
  482. foreach ($persistKeys as $k) {
  483. $c[$k] = $this->getDefault($this->getCanonicalPath($k));
  484. }
  485. foreach ($this->cfgUpdateReadMapping as $mapTo => $mapFrom) {
  486. if (! isset($c[$mapFrom])) {
  487. continue;
  488. }
  489. $c[$mapTo] = $c[$mapFrom];
  490. unset($c[$mapFrom]);
  491. }
  492. return $c;
  493. }
  494. }