ConfigFile.php 15 KB

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