AllConfig.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC;
  8. use OCP\Cache\CappedMemoryCache;
  9. use OCP\DB\QueryBuilder\IQueryBuilder;
  10. use OCP\IConfig;
  11. use OCP\IDBConnection;
  12. use OCP\PreConditionNotMetException;
  13. /**
  14. * Class to combine all the configuration options ownCloud offers
  15. */
  16. class AllConfig implements IConfig {
  17. private ?IDBConnection $connection = null;
  18. /**
  19. * 3 dimensional array with the following structure:
  20. * [ $userId =>
  21. * [ $appId =>
  22. * [ $key => $value ]
  23. * ]
  24. * ]
  25. *
  26. * database table: preferences
  27. *
  28. * methods that use this:
  29. * - setUserValue
  30. * - getUserValue
  31. * - getUserKeys
  32. * - deleteUserValue
  33. * - deleteAllUserValues
  34. * - deleteAppFromAllUsers
  35. *
  36. * @var CappedMemoryCache $userCache
  37. */
  38. private CappedMemoryCache $userCache;
  39. public function __construct(
  40. private SystemConfig $systemConfig,
  41. ) {
  42. $this->userCache = new CappedMemoryCache();
  43. }
  44. /**
  45. * TODO - FIXME This fixes an issue with base.php that cause cyclic
  46. * dependencies, especially with autoconfig setup
  47. *
  48. * Replace this by properly injected database connection. Currently the
  49. * base.php triggers the getDatabaseConnection too early which causes in
  50. * autoconfig setup case a too early distributed database connection and
  51. * the autoconfig then needs to reinit all already initialized dependencies
  52. * that use the database connection.
  53. *
  54. * otherwise a SQLite database is created in the wrong directory
  55. * because the database connection was created with an uninitialized config
  56. */
  57. private function fixDIInit() {
  58. if ($this->connection === null) {
  59. $this->connection = \OC::$server->get(IDBConnection::class);
  60. }
  61. }
  62. /**
  63. * Sets and deletes system wide values
  64. *
  65. * @param array $configs Associative array with `key => value` pairs
  66. * If value is null, the config key will be deleted
  67. */
  68. public function setSystemValues(array $configs) {
  69. $this->systemConfig->setValues($configs);
  70. }
  71. /**
  72. * Sets a new system wide value
  73. *
  74. * @param string $key the key of the value, under which will be saved
  75. * @param mixed $value the value that should be stored
  76. */
  77. public function setSystemValue($key, $value) {
  78. $this->systemConfig->setValue($key, $value);
  79. }
  80. /**
  81. * Looks up a system wide defined value
  82. *
  83. * @param string $key the key of the value, under which it was saved
  84. * @param mixed $default the default value to be returned if the value isn't set
  85. * @return mixed the value or $default
  86. */
  87. public function getSystemValue($key, $default = '') {
  88. return $this->systemConfig->getValue($key, $default);
  89. }
  90. /**
  91. * Looks up a boolean system wide defined value
  92. *
  93. * @param string $key the key of the value, under which it was saved
  94. * @param bool $default the default value to be returned if the value isn't set
  95. *
  96. * @return bool
  97. *
  98. * @since 16.0.0
  99. */
  100. public function getSystemValueBool(string $key, bool $default = false): bool {
  101. return (bool)$this->getSystemValue($key, $default);
  102. }
  103. /**
  104. * Looks up an integer system wide defined value
  105. *
  106. * @param string $key the key of the value, under which it was saved
  107. * @param int $default the default value to be returned if the value isn't set
  108. *
  109. * @return int
  110. *
  111. * @since 16.0.0
  112. */
  113. public function getSystemValueInt(string $key, int $default = 0): int {
  114. return (int)$this->getSystemValue($key, $default);
  115. }
  116. /**
  117. * Looks up a string system wide defined value
  118. *
  119. * @param string $key the key of the value, under which it was saved
  120. * @param string $default the default value to be returned if the value isn't set
  121. *
  122. * @return string
  123. *
  124. * @since 16.0.0
  125. */
  126. public function getSystemValueString(string $key, string $default = ''): string {
  127. return (string)$this->getSystemValue($key, $default);
  128. }
  129. /**
  130. * Looks up a system wide defined value and filters out sensitive data
  131. *
  132. * @param string $key the key of the value, under which it was saved
  133. * @param mixed $default the default value to be returned if the value isn't set
  134. * @return mixed the value or $default
  135. */
  136. public function getFilteredSystemValue($key, $default = '') {
  137. return $this->systemConfig->getFilteredValue($key, $default);
  138. }
  139. /**
  140. * Delete a system wide defined value
  141. *
  142. * @param string $key the key of the value, under which it was saved
  143. */
  144. public function deleteSystemValue($key) {
  145. $this->systemConfig->deleteValue($key);
  146. }
  147. /**
  148. * Get all keys stored for an app
  149. *
  150. * @param string $appName the appName that we stored the value under
  151. * @return string[] the keys stored for the app
  152. * @deprecated 29.0.0 Use {@see IAppConfig} directly
  153. */
  154. public function getAppKeys($appName) {
  155. return \OC::$server->get(AppConfig::class)->getKeys($appName);
  156. }
  157. /**
  158. * Writes a new app wide value
  159. *
  160. * @param string $appName the appName that we want to store the value under
  161. * @param string $key the key of the value, under which will be saved
  162. * @param string|float|int $value the value that should be stored
  163. * @deprecated 29.0.0 Use {@see IAppConfig} directly
  164. */
  165. public function setAppValue($appName, $key, $value) {
  166. \OC::$server->get(AppConfig::class)->setValue($appName, $key, $value);
  167. }
  168. /**
  169. * Looks up an app wide defined value
  170. *
  171. * @param string $appName the appName that we stored the value under
  172. * @param string $key the key of the value, under which it was saved
  173. * @param string $default the default value to be returned if the value isn't set
  174. * @return string the saved value
  175. * @deprecated 29.0.0 Use {@see IAppConfig} directly
  176. */
  177. public function getAppValue($appName, $key, $default = '') {
  178. return \OC::$server->get(AppConfig::class)->getValue($appName, $key, $default);
  179. }
  180. /**
  181. * Delete an app wide defined value
  182. *
  183. * @param string $appName the appName that we stored the value under
  184. * @param string $key the key of the value, under which it was saved
  185. * @deprecated 29.0.0 Use {@see IAppConfig} directly
  186. */
  187. public function deleteAppValue($appName, $key) {
  188. \OC::$server->get(AppConfig::class)->deleteKey($appName, $key);
  189. }
  190. /**
  191. * Removes all keys in appconfig belonging to the app
  192. *
  193. * @param string $appName the appName the configs are stored under
  194. * @deprecated 29.0.0 Use {@see IAppConfig} directly
  195. */
  196. public function deleteAppValues($appName) {
  197. \OC::$server->get(AppConfig::class)->deleteApp($appName);
  198. }
  199. /**
  200. * Set a user defined value
  201. *
  202. * @param string $userId the userId of the user that we want to store the value under
  203. * @param string $appName the appName that we want to store the value under
  204. * @param string $key the key under which the value is being stored
  205. * @param string|float|int $value the value that you want to store
  206. * @param string $preCondition only update if the config value was previously the value passed as $preCondition
  207. * @throws \OCP\PreConditionNotMetException if a precondition is specified and is not met
  208. * @throws \UnexpectedValueException when trying to store an unexpected value
  209. */
  210. public function setUserValue($userId, $appName, $key, $value, $preCondition = null) {
  211. if (!is_int($value) && !is_float($value) && !is_string($value)) {
  212. throw new \UnexpectedValueException('Only integers, floats and strings are allowed as value');
  213. }
  214. // TODO - FIXME
  215. $this->fixDIInit();
  216. if ($appName === 'settings' && $key === 'email') {
  217. $value = strtolower((string)$value);
  218. }
  219. $prevValue = $this->getUserValue($userId, $appName, $key, null);
  220. if ($prevValue !== null) {
  221. if ($preCondition !== null && $prevValue !== (string)$preCondition) {
  222. throw new PreConditionNotMetException();
  223. } elseif ($prevValue === (string)$value) {
  224. return;
  225. } else {
  226. $qb = $this->connection->getQueryBuilder();
  227. $qb->update('preferences')
  228. ->set('configvalue', $qb->createNamedParameter($value))
  229. ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)))
  230. ->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter($appName)))
  231. ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
  232. $qb->executeStatement();
  233. $this->userCache[$userId][$appName][$key] = (string)$value;
  234. return;
  235. }
  236. }
  237. $preconditionArray = [];
  238. if (isset($preCondition)) {
  239. $preconditionArray = [
  240. 'configvalue' => $preCondition,
  241. ];
  242. }
  243. $this->connection->setValues('preferences', [
  244. 'userid' => $userId,
  245. 'appid' => $appName,
  246. 'configkey' => $key,
  247. ], [
  248. 'configvalue' => $value,
  249. ], $preconditionArray);
  250. // only add to the cache if we already loaded data for the user
  251. if (isset($this->userCache[$userId])) {
  252. if (!isset($this->userCache[$userId][$appName])) {
  253. $this->userCache[$userId][$appName] = [];
  254. }
  255. $this->userCache[$userId][$appName][$key] = (string)$value;
  256. }
  257. }
  258. /**
  259. * Getting a user defined value
  260. *
  261. * @param ?string $userId the userId of the user that we want to store the value under
  262. * @param string $appName the appName that we stored the value under
  263. * @param string $key the key under which the value is being stored
  264. * @param mixed $default the default value to be returned if the value isn't set
  265. * @return string
  266. */
  267. public function getUserValue($userId, $appName, $key, $default = '') {
  268. $data = $this->getAllUserValues($userId);
  269. if (isset($data[$appName][$key])) {
  270. return $data[$appName][$key];
  271. } else {
  272. return $default;
  273. }
  274. }
  275. /**
  276. * Get the keys of all stored by an app for the user
  277. *
  278. * @param string $userId the userId of the user that we want to store the value under
  279. * @param string $appName the appName that we stored the value under
  280. * @return string[]
  281. */
  282. public function getUserKeys($userId, $appName) {
  283. $data = $this->getAllUserValues($userId);
  284. if (isset($data[$appName])) {
  285. return array_map('strval', array_keys($data[$appName]));
  286. } else {
  287. return [];
  288. }
  289. }
  290. /**
  291. * Delete a user value
  292. *
  293. * @param string $userId the userId of the user that we want to store the value under
  294. * @param string $appName the appName that we stored the value under
  295. * @param string $key the key under which the value is being stored
  296. */
  297. public function deleteUserValue($userId, $appName, $key) {
  298. // TODO - FIXME
  299. $this->fixDIInit();
  300. $qb = $this->connection->getQueryBuilder();
  301. $qb->delete('preferences')
  302. ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
  303. ->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR)))
  304. ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key, IQueryBuilder::PARAM_STR)))
  305. ->executeStatement();
  306. if (isset($this->userCache[$userId][$appName])) {
  307. unset($this->userCache[$userId][$appName][$key]);
  308. }
  309. }
  310. /**
  311. * Delete all user values
  312. *
  313. * @param string $userId the userId of the user that we want to remove all values from
  314. */
  315. public function deleteAllUserValues($userId) {
  316. // TODO - FIXME
  317. $this->fixDIInit();
  318. $qb = $this->connection->getQueryBuilder();
  319. $qb->delete('preferences')
  320. ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
  321. ->executeStatement();
  322. unset($this->userCache[$userId]);
  323. }
  324. /**
  325. * Delete all user related values of one app
  326. *
  327. * @param string $appName the appName of the app that we want to remove all values from
  328. */
  329. public function deleteAppFromAllUsers($appName) {
  330. // TODO - FIXME
  331. $this->fixDIInit();
  332. $qb = $this->connection->getQueryBuilder();
  333. $qb->delete('preferences')
  334. ->where($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR)))
  335. ->executeStatement();
  336. foreach ($this->userCache as &$userCache) {
  337. unset($userCache[$appName]);
  338. }
  339. }
  340. /**
  341. * Returns all user configs sorted by app of one user
  342. *
  343. * @param ?string $userId the user ID to get the app configs from
  344. * @psalm-return array<string, array<string, string>>
  345. * @return array[] - 2 dimensional array with the following structure:
  346. * [ $appId =>
  347. * [ $key => $value ]
  348. * ]
  349. */
  350. public function getAllUserValues(?string $userId): array {
  351. if (isset($this->userCache[$userId])) {
  352. return $this->userCache[$userId];
  353. }
  354. if ($userId === null || $userId === '') {
  355. $this->userCache[''] = [];
  356. return $this->userCache[''];
  357. }
  358. // TODO - FIXME
  359. $this->fixDIInit();
  360. $data = [];
  361. $qb = $this->connection->getQueryBuilder();
  362. $result = $qb->select('appid', 'configkey', 'configvalue')
  363. ->from('preferences')
  364. ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
  365. ->executeQuery();
  366. while ($row = $result->fetch()) {
  367. $appId = $row['appid'];
  368. if (!isset($data[$appId])) {
  369. $data[$appId] = [];
  370. }
  371. $data[$appId][$row['configkey']] = $row['configvalue'];
  372. }
  373. $this->userCache[$userId] = $data;
  374. return $data;
  375. }
  376. /**
  377. * Fetches a mapped list of userId -> value, for a specified app and key and a list of user IDs.
  378. *
  379. * @param string $appName app to get the value for
  380. * @param string $key the key to get the value for
  381. * @param array $userIds the user IDs to fetch the values for
  382. * @return array Mapped values: userId => value
  383. */
  384. public function getUserValueForUsers($appName, $key, $userIds) {
  385. // TODO - FIXME
  386. $this->fixDIInit();
  387. if (empty($userIds) || !is_array($userIds)) {
  388. return [];
  389. }
  390. $chunkedUsers = array_chunk($userIds, 50, true);
  391. $qb = $this->connection->getQueryBuilder();
  392. $qb->select('userid', 'configvalue')
  393. ->from('preferences')
  394. ->where($qb->expr()->eq('appid', $qb->createParameter('appName')))
  395. ->andWhere($qb->expr()->eq('configkey', $qb->createParameter('configKey')))
  396. ->andWhere($qb->expr()->in('userid', $qb->createParameter('userIds')));
  397. $userValues = [];
  398. foreach ($chunkedUsers as $chunk) {
  399. $qb->setParameter('appName', $appName, IQueryBuilder::PARAM_STR);
  400. $qb->setParameter('configKey', $key, IQueryBuilder::PARAM_STR);
  401. $qb->setParameter('userIds', $chunk, IQueryBuilder::PARAM_STR_ARRAY);
  402. $result = $qb->executeQuery();
  403. while ($row = $result->fetch()) {
  404. $userValues[$row['userid']] = $row['configvalue'];
  405. }
  406. }
  407. return $userValues;
  408. }
  409. /**
  410. * Determines the users that have the given value set for a specific app-key-pair
  411. *
  412. * @param string $appName the app to get the user for
  413. * @param string $key the key to get the user for
  414. * @param string $value the value to get the user for
  415. * @return list<string> of user IDs
  416. */
  417. public function getUsersForUserValue($appName, $key, $value) {
  418. // TODO - FIXME
  419. $this->fixDIInit();
  420. $qb = $this->connection->getQueryBuilder();
  421. $configValueColumn = ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE)
  422. ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR)
  423. : 'configvalue';
  424. $result = $qb->select('userid')
  425. ->from('preferences')
  426. ->where($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR)))
  427. ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key, IQueryBuilder::PARAM_STR)))
  428. ->andWhere($qb->expr()->eq(
  429. $configValueColumn,
  430. $qb->createNamedParameter($value, IQueryBuilder::PARAM_STR))
  431. )->orderBy('userid')
  432. ->executeQuery();
  433. $userIDs = [];
  434. while ($row = $result->fetch()) {
  435. $userIDs[] = $row['userid'];
  436. }
  437. return $userIDs;
  438. }
  439. /**
  440. * Determines the users that have the given value set for a specific app-key-pair
  441. *
  442. * @param string $appName the app to get the user for
  443. * @param string $key the key to get the user for
  444. * @param string $value the value to get the user for
  445. * @return list<string> of user IDs
  446. */
  447. public function getUsersForUserValueCaseInsensitive($appName, $key, $value) {
  448. // TODO - FIXME
  449. $this->fixDIInit();
  450. if ($appName === 'settings' && $key === 'email') {
  451. // Email address is always stored lowercase in the database
  452. return $this->getUsersForUserValue($appName, $key, strtolower($value));
  453. }
  454. $qb = $this->connection->getQueryBuilder();
  455. $configValueColumn = ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_ORACLE)
  456. ? $qb->expr()->castColumn('configvalue', IQueryBuilder::PARAM_STR)
  457. : 'configvalue';
  458. $result = $qb->select('userid')
  459. ->from('preferences')
  460. ->where($qb->expr()->eq('appid', $qb->createNamedParameter($appName, IQueryBuilder::PARAM_STR)))
  461. ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key, IQueryBuilder::PARAM_STR)))
  462. ->andWhere($qb->expr()->eq(
  463. $qb->func()->lower($configValueColumn),
  464. $qb->createNamedParameter(strtolower($value), IQueryBuilder::PARAM_STR))
  465. )->orderBy('userid')
  466. ->executeQuery();
  467. $userIDs = [];
  468. while ($row = $result->fetch()) {
  469. $userIDs[] = $row['userid'];
  470. }
  471. return $userIDs;
  472. }
  473. public function getSystemConfig() {
  474. return $this->systemConfig;
  475. }
  476. }