ConfigFile.php 14 KB

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