UserPreferences.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin;
  4. use PhpMyAdmin\Config\ConfigFile;
  5. use PhpMyAdmin\Config\Forms\User\UserFormList;
  6. use function array_flip;
  7. use function array_merge;
  8. use function basename;
  9. use function http_build_query;
  10. use function is_array;
  11. use function json_decode;
  12. use function json_encode;
  13. use function strpos;
  14. use function time;
  15. use function urlencode;
  16. /**
  17. * Functions for displaying user preferences pages
  18. */
  19. class UserPreferences
  20. {
  21. /** @var Relation */
  22. private $relation;
  23. /** @var Template */
  24. public $template;
  25. public function __construct()
  26. {
  27. global $dbi;
  28. $this->relation = new Relation($dbi);
  29. $this->template = new Template();
  30. }
  31. /**
  32. * Common initialization for user preferences modification pages
  33. *
  34. * @param ConfigFile $cf Config file instance
  35. *
  36. * @return void
  37. */
  38. public function pageInit(ConfigFile $cf)
  39. {
  40. $forms_all_keys = UserFormList::getFields();
  41. $cf->resetConfigData(); // start with a clean instance
  42. $cf->setAllowedKeys($forms_all_keys);
  43. $cf->setCfgUpdateReadMapping(
  44. [
  45. 'Server/hide_db' => 'Servers/1/hide_db',
  46. 'Server/only_db' => 'Servers/1/only_db',
  47. ]
  48. );
  49. $cf->updateWithGlobalConfig($GLOBALS['cfg']);
  50. }
  51. /**
  52. * Loads user preferences
  53. *
  54. * Returns an array:
  55. * * config_data - path => value pairs
  56. * * mtime - last modification time
  57. * * type - 'db' (config read from pmadb) or 'session' (read from user session)
  58. *
  59. * @return array
  60. */
  61. public function load()
  62. {
  63. global $dbi;
  64. $cfgRelation = $this->relation->getRelationsParam();
  65. if (! $cfgRelation['userconfigwork']) {
  66. // no pmadb table, use session storage
  67. if (! isset($_SESSION['userconfig'])) {
  68. $_SESSION['userconfig'] = [
  69. 'db' => [],
  70. 'ts' => time(),
  71. ];
  72. }
  73. return [
  74. 'config_data' => $_SESSION['userconfig']['db'],
  75. 'mtime' => $_SESSION['userconfig']['ts'],
  76. 'type' => 'session',
  77. ];
  78. }
  79. // load configuration from pmadb
  80. $query_table = Util::backquote($cfgRelation['db']) . '.'
  81. . Util::backquote($cfgRelation['userconfig']);
  82. $query = 'SELECT `config_data`, UNIX_TIMESTAMP(`timevalue`) ts'
  83. . ' FROM ' . $query_table
  84. . ' WHERE `username` = \''
  85. . $dbi->escapeString($cfgRelation['user'])
  86. . '\'';
  87. $row = $dbi->fetchSingleRow($query, 'ASSOC', DatabaseInterface::CONNECT_CONTROL);
  88. return [
  89. 'config_data' => $row ? json_decode($row['config_data'], true) : [],
  90. 'mtime' => $row ? $row['ts'] : time(),
  91. 'type' => 'db',
  92. ];
  93. }
  94. /**
  95. * Saves user preferences
  96. *
  97. * @param array $config_array configuration array
  98. *
  99. * @return true|Message
  100. */
  101. public function save(array $config_array)
  102. {
  103. global $dbi;
  104. $cfgRelation = $this->relation->getRelationsParam();
  105. $server = $GLOBALS['server'] ?? $GLOBALS['cfg']['ServerDefault'];
  106. $cache_key = 'server_' . $server;
  107. if (! $cfgRelation['userconfigwork']) {
  108. // no pmadb table, use session storage
  109. $_SESSION['userconfig'] = [
  110. 'db' => $config_array,
  111. 'ts' => time(),
  112. ];
  113. if (isset($_SESSION['cache'][$cache_key]['userprefs'])) {
  114. unset($_SESSION['cache'][$cache_key]['userprefs']);
  115. }
  116. return true;
  117. }
  118. // save configuration to pmadb
  119. $query_table = Util::backquote($cfgRelation['db']) . '.'
  120. . Util::backquote($cfgRelation['userconfig']);
  121. $query = 'SELECT `username` FROM ' . $query_table
  122. . ' WHERE `username` = \''
  123. . $dbi->escapeString($cfgRelation['user'])
  124. . '\'';
  125. $has_config = $dbi->fetchValue(
  126. $query,
  127. 0,
  128. 0,
  129. DatabaseInterface::CONNECT_CONTROL
  130. );
  131. $config_data = json_encode($config_array);
  132. if ($has_config) {
  133. $query = 'UPDATE ' . $query_table
  134. . ' SET `timevalue` = NOW(), `config_data` = \''
  135. . $dbi->escapeString($config_data)
  136. . '\''
  137. . ' WHERE `username` = \''
  138. . $dbi->escapeString($cfgRelation['user'])
  139. . '\'';
  140. } else {
  141. $query = 'INSERT INTO ' . $query_table
  142. . ' (`username`, `timevalue`,`config_data`) '
  143. . 'VALUES (\''
  144. . $dbi->escapeString($cfgRelation['user']) . '\', NOW(), '
  145. . '\'' . $dbi->escapeString($config_data) . '\')';
  146. }
  147. if (isset($_SESSION['cache'][$cache_key]['userprefs'])) {
  148. unset($_SESSION['cache'][$cache_key]['userprefs']);
  149. }
  150. if (! $dbi->tryQuery($query, DatabaseInterface::CONNECT_CONTROL)) {
  151. $message = Message::error(__('Could not save configuration'));
  152. $message->addMessage(
  153. Message::rawError(
  154. $dbi->getError(DatabaseInterface::CONNECT_CONTROL)
  155. ),
  156. '<br><br>'
  157. );
  158. return $message;
  159. }
  160. return true;
  161. }
  162. /**
  163. * Returns a user preferences array filtered by $cfg['UserprefsDisallow']
  164. * (exclude list) and keys from user preferences form (allow list)
  165. *
  166. * @param array $config_data path => value pairs
  167. *
  168. * @return array
  169. */
  170. public function apply(array $config_data)
  171. {
  172. $cfg = [];
  173. $excludeList = array_flip($GLOBALS['cfg']['UserprefsDisallow']);
  174. $allowList = array_flip(UserFormList::getFields());
  175. // allow some additional fields which are custom handled
  176. $allowList['ThemeDefault'] = true;
  177. $allowList['lang'] = true;
  178. $allowList['Server/hide_db'] = true;
  179. $allowList['Server/only_db'] = true;
  180. $allowList['2fa'] = true;
  181. foreach ($config_data as $path => $value) {
  182. if (! isset($allowList[$path]) || isset($excludeList[$path])) {
  183. continue;
  184. }
  185. Core::arrayWrite($path, $cfg, $value);
  186. }
  187. return $cfg;
  188. }
  189. /**
  190. * Updates one user preferences option (loads and saves to database).
  191. *
  192. * No validation is done!
  193. *
  194. * @param string $path configuration
  195. * @param mixed $value value
  196. * @param mixed $default_value default value
  197. *
  198. * @return true|Message
  199. */
  200. public function persistOption($path, $value, $default_value)
  201. {
  202. $prefs = $this->load();
  203. if ($value === $default_value) {
  204. if (! isset($prefs['config_data'][$path])) {
  205. return true;
  206. }
  207. unset($prefs['config_data'][$path]);
  208. } else {
  209. $prefs['config_data'][$path] = $value;
  210. }
  211. return $this->save($prefs['config_data']);
  212. }
  213. /**
  214. * Redirects after saving new user preferences
  215. *
  216. * @param string $file_name Filename
  217. * @param array|null $params URL parameters
  218. * @param string $hash Hash value
  219. *
  220. * @return void
  221. */
  222. public function redirect(
  223. $file_name,
  224. $params = null,
  225. $hash = null
  226. ) {
  227. // redirect
  228. $url_params = ['saved' => 1];
  229. if (is_array($params)) {
  230. $url_params = array_merge($params, $url_params);
  231. }
  232. if ($hash) {
  233. $hash = '#' . urlencode($hash);
  234. }
  235. Core::sendHeaderLocation('./' . $file_name
  236. . Url::getCommonRaw($url_params, strpos($file_name, '?') === false ? '?' : '&') . $hash);
  237. }
  238. /**
  239. * Shows form which allows to quickly load
  240. * settings stored in browser's local storage
  241. *
  242. * @return string
  243. */
  244. public function autoloadGetHeader()
  245. {
  246. if (isset($_REQUEST['prefs_autoload'])
  247. && $_REQUEST['prefs_autoload'] === 'hide'
  248. ) {
  249. $_SESSION['userprefs_autoload'] = true;
  250. return '';
  251. }
  252. $script_name = basename(basename($GLOBALS['PMA_PHP_SELF']));
  253. $return_url = $script_name . '?' . http_build_query($_GET, '', '&');
  254. return $this->template->render('preferences/autoload', [
  255. 'hidden_inputs' => Url::getHiddenInputs(),
  256. 'return_url' => $return_url,
  257. ]);
  258. }
  259. }