AppConfig.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2017, Joas Schilling <coding@schilljs.com>
  5. * @copyright Copyright (c) 2016, ownCloud, Inc.
  6. *
  7. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  8. * @author Bart Visscher <bartv@thisnet.nl>
  9. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  10. * @author Jakob Sack <mail@jakobsack.de>
  11. * @author Joas Schilling <coding@schilljs.com>
  12. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  13. * @author Maxence Lange <maxence@artificial-owl.com>
  14. * @author michaelletzgus <michaelletzgus@users.noreply.github.com>
  15. * @author Morris Jobke <hey@morrisjobke.de>
  16. * @author Robin Appelman <robin@icewind.nl>
  17. * @author Robin McCorkell <robin@mccorkell.me.uk>
  18. * @author Roeland Jago Douma <roeland@famdouma.nl>
  19. *
  20. * @license AGPL-3.0
  21. *
  22. * This code is free software: you can redistribute it and/or modify
  23. * it under the terms of the GNU Affero General Public License, version 3,
  24. * as published by the Free Software Foundation.
  25. *
  26. * This program is distributed in the hope that it will be useful,
  27. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  28. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  29. * GNU Affero General Public License for more details.
  30. *
  31. * You should have received a copy of the GNU Affero General Public License, version 3,
  32. * along with this program. If not, see <http://www.gnu.org/licenses/>
  33. *
  34. */
  35. namespace OC;
  36. use InvalidArgumentException;
  37. use JsonException;
  38. use OCP\DB\Exception as DBException;
  39. use OCP\DB\QueryBuilder\IQueryBuilder;
  40. use OCP\Exceptions\AppConfigIncorrectTypeException;
  41. use OCP\Exceptions\AppConfigTypeConflictException;
  42. use OCP\Exceptions\AppConfigUnknownKeyException;
  43. use OCP\IAppConfig;
  44. use OCP\IConfig;
  45. use OCP\IDBConnection;
  46. use OCP\Security\ICrypto;
  47. use Psr\Log\LoggerInterface;
  48. /**
  49. * This class provides an easy way for apps to store config values in the
  50. * database.
  51. *
  52. * **Note:** since 29.0.0, it supports **lazy loading**
  53. *
  54. * ### What is lazy loading ?
  55. * In order to avoid loading useless config values into memory for each request,
  56. * only non-lazy values are now loaded.
  57. *
  58. * Once a value that is lazy is requested, all lazy values will be loaded.
  59. *
  60. * Similarly, some methods from this class are marked with a warning about ignoring
  61. * lazy loading. Use them wisely and only on parts of the code that are called
  62. * during specific requests or actions to avoid loading the lazy values all the time.
  63. *
  64. * @since 7.0.0
  65. * @since 29.0.0 - Supporting types and lazy loading
  66. */
  67. class AppConfig implements IAppConfig {
  68. private const APP_MAX_LENGTH = 32;
  69. private const KEY_MAX_LENGTH = 64;
  70. private const ENCRYPTION_PREFIX = '$AppConfigEncryption$';
  71. private const ENCRYPTION_PREFIX_LENGTH = 21; // strlen(self::ENCRYPTION_PREFIX)
  72. /** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
  73. private array $fastCache = []; // cache for normal config keys
  74. /** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
  75. private array $lazyCache = []; // cache for lazy config keys
  76. /** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
  77. private array $valueTypes = []; // type for all config values
  78. private bool $fastLoaded = false;
  79. private bool $lazyLoaded = false;
  80. /**
  81. * $migrationCompleted is only needed to manage the previous structure
  82. * of the database during the upgrading process to nc29.
  83. *
  84. * only when upgrading from a version prior 28.0.2
  85. *
  86. * @TODO: remove this value in Nextcloud 30+
  87. */
  88. private bool $migrationCompleted = true;
  89. public function __construct(
  90. protected IDBConnection $connection,
  91. protected LoggerInterface $logger,
  92. protected ICrypto $crypto,
  93. ) {
  94. }
  95. /**
  96. * @inheritDoc
  97. *
  98. * @return string[] list of app ids
  99. * @since 7.0.0
  100. */
  101. public function getApps(): array {
  102. $this->loadConfigAll();
  103. $apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
  104. sort($apps);
  105. return array_values(array_unique($apps));
  106. }
  107. /**
  108. * @inheritDoc
  109. *
  110. * @param string $app id of the app
  111. *
  112. * @return string[] list of stored config keys
  113. * @since 29.0.0
  114. */
  115. public function getKeys(string $app): array {
  116. $this->assertParams($app);
  117. $this->loadConfigAll();
  118. $keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
  119. sort($keys);
  120. return array_values(array_unique($keys));
  121. }
  122. /**
  123. * @inheritDoc
  124. *
  125. * @param string $app id of the app
  126. * @param string $key config key
  127. * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
  128. *
  129. * @return bool TRUE if key exists
  130. * @since 7.0.0
  131. * @since 29.0.0 Added the $lazy argument
  132. */
  133. public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
  134. $this->assertParams($app, $key);
  135. $this->loadConfig($lazy);
  136. if ($lazy === null) {
  137. $appCache = $this->getAllValues($app);
  138. return isset($appCache[$key]);
  139. }
  140. if ($lazy) {
  141. return isset($this->lazyCache[$app][$key]);
  142. }
  143. return isset($this->fastCache[$app][$key]);
  144. }
  145. /**
  146. * @param string $app id of the app
  147. * @param string $key config key
  148. * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
  149. *
  150. * @return bool
  151. * @throws AppConfigUnknownKeyException if config key is not known
  152. * @since 29.0.0
  153. */
  154. public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
  155. $this->assertParams($app, $key);
  156. $this->loadConfig($lazy);
  157. if (!isset($this->valueTypes[$app][$key])) {
  158. throw new AppConfigUnknownKeyException('unknown config key');
  159. }
  160. return $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key]);
  161. }
  162. /**
  163. * @inheritDoc
  164. *
  165. * @param string $app if of the app
  166. * @param string $key config key
  167. *
  168. * @return bool TRUE if config is lazy loaded
  169. * @throws AppConfigUnknownKeyException if config key is not known
  170. * @see IAppConfig for details about lazy loading
  171. * @since 29.0.0
  172. */
  173. public function isLazy(string $app, string $key): bool {
  174. // there is a huge probability the non-lazy config are already loaded
  175. if ($this->hasKey($app, $key, false)) {
  176. return false;
  177. }
  178. // key not found, we search in the lazy config
  179. if ($this->hasKey($app, $key, true)) {
  180. return true;
  181. }
  182. throw new AppConfigUnknownKeyException('unknown config key');
  183. }
  184. /**
  185. * @inheritDoc
  186. *
  187. * @param string $app id of the app
  188. * @param string $prefix config keys prefix to search
  189. * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
  190. *
  191. * @return array<string, string> [configKey => configValue]
  192. * @since 29.0.0
  193. */
  194. public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
  195. $this->assertParams($app, $prefix);
  196. // if we want to filter values, we need to get sensitivity
  197. $this->loadConfigAll();
  198. // array_merge() will remove numeric keys (here config keys), so addition arrays instead
  199. $values = array_filter(
  200. (($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? [])),
  201. function (string $key) use ($prefix): bool {
  202. return str_starts_with($key, $prefix); // filter values based on $prefix
  203. }, ARRAY_FILTER_USE_KEY
  204. );
  205. if (!$filtered) {
  206. return $values;
  207. }
  208. /**
  209. * Using the old (deprecated) list of sensitive values.
  210. */
  211. foreach ($this->getSensitiveKeys($app) as $sensitiveKeyExp) {
  212. $sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
  213. foreach ($sensitiveKeys as $sensitiveKey) {
  214. $this->valueTypes[$app][$sensitiveKey] = ($this->valueTypes[$app][$sensitiveKey] ?? 0) | self::VALUE_SENSITIVE;
  215. }
  216. }
  217. $result = [];
  218. foreach ($values as $key => $value) {
  219. $result[$key] = $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key] ?? 0) ? IConfig::SENSITIVE_VALUE : $value;
  220. }
  221. return $result;
  222. }
  223. /**
  224. * @inheritDoc
  225. *
  226. * @param string $key config key
  227. * @param bool $lazy search within lazy loaded config
  228. *
  229. * @return array<string, string> [appId => configValue]
  230. * @since 29.0.0
  231. */
  232. public function searchValues(string $key, bool $lazy = false): array {
  233. $this->assertParams('', $key, true);
  234. $this->loadConfig($lazy);
  235. $values = [];
  236. /** @var array<array-key, array<array-key, mixed>> $cache */
  237. if ($lazy) {
  238. $cache = $this->lazyCache;
  239. } else {
  240. $cache = $this->fastCache;
  241. }
  242. foreach (array_keys($cache) as $app) {
  243. if (isset($cache[$app][$key])) {
  244. $values[$app] = $cache[$app][$key];
  245. }
  246. }
  247. return $values;
  248. }
  249. /**
  250. * Get the config value as string.
  251. * If the value does not exist the given default will be returned.
  252. *
  253. * Set lazy to `null` to ignore it and get the value from either source.
  254. *
  255. * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
  256. *
  257. * @param string $app id of the app
  258. * @param string $key config key
  259. * @param string $default config value
  260. * @param null|bool $lazy get config as lazy loaded or not. can be NULL
  261. *
  262. * @return string the value or $default
  263. * @internal
  264. * @since 29.0.0
  265. * @see IAppConfig for explanation about lazy loading
  266. * @see getValueString()
  267. * @see getValueInt()
  268. * @see getValueFloat()
  269. * @see getValueBool()
  270. * @see getValueArray()
  271. */
  272. public function getValueMixed(
  273. string $app,
  274. string $key,
  275. string $default = '',
  276. ?bool $lazy = false
  277. ): string {
  278. try {
  279. $lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
  280. } catch (AppConfigUnknownKeyException $e) {
  281. return $default;
  282. }
  283. return $this->getTypedValue(
  284. $app,
  285. $key,
  286. $default,
  287. $lazy,
  288. self::VALUE_MIXED
  289. );
  290. }
  291. /**
  292. * @inheritDoc
  293. *
  294. * @param string $app id of the app
  295. * @param string $key config key
  296. * @param string $default default value
  297. * @param bool $lazy search within lazy loaded config
  298. *
  299. * @return string stored config value or $default if not set in database
  300. * @throws InvalidArgumentException if one of the argument format is invalid
  301. * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
  302. * @since 29.0.0
  303. * @see IAppConfig for explanation about lazy loading
  304. */
  305. public function getValueString(
  306. string $app,
  307. string $key,
  308. string $default = '',
  309. bool $lazy = false
  310. ): string {
  311. return $this->getTypedValue($app, $key, $default, $lazy, self::VALUE_STRING);
  312. }
  313. /**
  314. * @inheritDoc
  315. *
  316. * @param string $app id of the app
  317. * @param string $key config key
  318. * @param int $default default value
  319. * @param bool $lazy search within lazy loaded config
  320. *
  321. * @return int stored config value or $default if not set in database
  322. * @throws InvalidArgumentException if one of the argument format is invalid
  323. * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
  324. * @since 29.0.0
  325. * @see IAppConfig for explanation about lazy loading
  326. */
  327. public function getValueInt(
  328. string $app,
  329. string $key,
  330. int $default = 0,
  331. bool $lazy = false
  332. ): int {
  333. return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
  334. }
  335. /**
  336. * @inheritDoc
  337. *
  338. * @param string $app id of the app
  339. * @param string $key config key
  340. * @param float $default default value
  341. * @param bool $lazy search within lazy loaded config
  342. *
  343. * @return float stored config value or $default if not set in database
  344. * @throws InvalidArgumentException if one of the argument format is invalid
  345. * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
  346. * @since 29.0.0
  347. * @see IAppConfig for explanation about lazy loading
  348. */
  349. public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
  350. return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
  351. }
  352. /**
  353. * @inheritDoc
  354. *
  355. * @param string $app id of the app
  356. * @param string $key config key
  357. * @param bool $default default value
  358. * @param bool $lazy search within lazy loaded config
  359. *
  360. * @return bool stored config value or $default if not set in database
  361. * @throws InvalidArgumentException if one of the argument format is invalid
  362. * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
  363. * @since 29.0.0
  364. * @see IAppConfig for explanation about lazy loading
  365. */
  366. public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
  367. $b = strtolower($this->getTypedValue($app, $key, $default ? 'true' : 'false', $lazy, self::VALUE_BOOL));
  368. return in_array($b, ['1', 'true', 'yes', 'on']);
  369. }
  370. /**
  371. * @inheritDoc
  372. *
  373. * @param string $app id of the app
  374. * @param string $key config key
  375. * @param array $default default value
  376. * @param bool $lazy search within lazy loaded config
  377. *
  378. * @return array stored config value or $default if not set in database
  379. * @throws InvalidArgumentException if one of the argument format is invalid
  380. * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
  381. * @since 29.0.0
  382. * @see IAppConfig for explanation about lazy loading
  383. */
  384. public function getValueArray(
  385. string $app,
  386. string $key,
  387. array $default = [],
  388. bool $lazy = false
  389. ): array {
  390. try {
  391. $defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
  392. $value = json_decode($this->getTypedValue($app, $key, $defaultJson, $lazy, self::VALUE_ARRAY), true, flags: JSON_THROW_ON_ERROR);
  393. return is_array($value) ? $value : [];
  394. } catch (JsonException) {
  395. return [];
  396. }
  397. }
  398. /**
  399. * @param string $app id of the app
  400. * @param string $key config key
  401. * @param string $default default value
  402. * @param bool $lazy search within lazy loaded config
  403. * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT}{@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
  404. *
  405. * @return string
  406. * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
  407. * @throws InvalidArgumentException
  408. */
  409. private function getTypedValue(
  410. string $app,
  411. string $key,
  412. string $default,
  413. bool $lazy,
  414. int $type
  415. ): string {
  416. $this->assertParams($app, $key, valueType: $type);
  417. $this->loadConfig($lazy);
  418. /**
  419. * We ignore check if mixed type is requested.
  420. * If type of stored value is set as mixed, we don't filter.
  421. * If type of stored value is defined, we compare with the one requested.
  422. */
  423. $knownType = $this->valueTypes[$app][$key] ?? 0;
  424. if (!$this->isTyped(self::VALUE_MIXED, $type)
  425. && $knownType > 0
  426. && !$this->isTyped(self::VALUE_MIXED, $knownType)
  427. && !$this->isTyped($type, $knownType)) {
  428. $this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
  429. throw new AppConfigTypeConflictException('conflict with value type from database');
  430. }
  431. /**
  432. * - the pair $app/$key cannot exist in both array,
  433. * - we should still return an existing non-lazy value even if current method
  434. * is called with $lazy is true
  435. *
  436. * This way, lazyCache will be empty until the load for lazy config value is requested.
  437. */
  438. if (isset($this->lazyCache[$app][$key])) {
  439. $value = $this->lazyCache[$app][$key];
  440. } elseif (isset($this->fastCache[$app][$key])) {
  441. $value = $this->fastCache[$app][$key];
  442. } else {
  443. return $default;
  444. }
  445. $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $knownType);
  446. if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
  447. // Only decrypt values that are stored encrypted
  448. $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
  449. }
  450. return $value;
  451. }
  452. /**
  453. * @inheritDoc
  454. *
  455. * @param string $app id of the app
  456. * @param string $key config key
  457. *
  458. * @return int type of the value
  459. * @throws AppConfigUnknownKeyException if config key is not known
  460. * @since 29.0.0
  461. * @see VALUE_STRING
  462. * @see VALUE_INT
  463. * @see VALUE_FLOAT
  464. * @see VALUE_BOOL
  465. * @see VALUE_ARRAY
  466. */
  467. public function getValueType(string $app, string $key): int {
  468. $this->assertParams($app, $key);
  469. $this->loadConfigAll();
  470. if (!isset($this->valueTypes[$app][$key])) {
  471. throw new AppConfigUnknownKeyException('unknown config key');
  472. }
  473. $type = $this->valueTypes[$app][$key];
  474. $type &= ~self::VALUE_SENSITIVE;
  475. return $type;
  476. }
  477. /**
  478. * Store a config key and its value in database as VALUE_MIXED
  479. *
  480. * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
  481. *
  482. * @param string $app id of the app
  483. * @param string $key config key
  484. * @param string $value config value
  485. * @param bool $lazy set config as lazy loaded
  486. * @param bool $sensitive if TRUE value will be hidden when listing config values.
  487. *
  488. * @return bool TRUE if value was different, therefor updated in database
  489. * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED
  490. * @internal
  491. * @since 29.0.0
  492. * @see IAppConfig for explanation about lazy loading
  493. * @see setValueString()
  494. * @see setValueInt()
  495. * @see setValueFloat()
  496. * @see setValueBool()
  497. * @see setValueArray()
  498. */
  499. public function setValueMixed(
  500. string $app,
  501. string $key,
  502. string $value,
  503. bool $lazy = false,
  504. bool $sensitive = false
  505. ): bool {
  506. return $this->setTypedValue(
  507. $app,
  508. $key,
  509. $value,
  510. $lazy,
  511. self::VALUE_MIXED | ($sensitive ? self::VALUE_SENSITIVE : 0)
  512. );
  513. }
  514. /**
  515. * @inheritDoc
  516. *
  517. * @param string $app id of the app
  518. * @param string $key config key
  519. * @param string $value config value
  520. * @param bool $lazy set config as lazy loaded
  521. * @param bool $sensitive if TRUE value will be hidden when listing config values.
  522. *
  523. * @return bool TRUE if value was different, therefor updated in database
  524. * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
  525. * @since 29.0.0
  526. * @see IAppConfig for explanation about lazy loading
  527. */
  528. public function setValueString(
  529. string $app,
  530. string $key,
  531. string $value,
  532. bool $lazy = false,
  533. bool $sensitive = false
  534. ): bool {
  535. return $this->setTypedValue(
  536. $app,
  537. $key,
  538. $value,
  539. $lazy,
  540. self::VALUE_STRING | ($sensitive ? self::VALUE_SENSITIVE : 0)
  541. );
  542. }
  543. /**
  544. * @inheritDoc
  545. *
  546. * @param string $app id of the app
  547. * @param string $key config key
  548. * @param int $value config value
  549. * @param bool $lazy set config as lazy loaded
  550. * @param bool $sensitive if TRUE value will be hidden when listing config values.
  551. *
  552. * @return bool TRUE if value was different, therefor updated in database
  553. * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
  554. * @since 29.0.0
  555. * @see IAppConfig for explanation about lazy loading
  556. */
  557. public function setValueInt(
  558. string $app,
  559. string $key,
  560. int $value,
  561. bool $lazy = false,
  562. bool $sensitive = false
  563. ): bool {
  564. if ($value > 2000000000) {
  565. $this->logger->debug('You are trying to store an integer value around/above 2,147,483,647. This is a reminder that reaching this theoretical limit on 32 bits system will throw an exception.');
  566. }
  567. return $this->setTypedValue(
  568. $app,
  569. $key,
  570. (string)$value,
  571. $lazy,
  572. self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
  573. );
  574. }
  575. /**
  576. * @inheritDoc
  577. *
  578. * @param string $app id of the app
  579. * @param string $key config key
  580. * @param float $value config value
  581. * @param bool $lazy set config as lazy loaded
  582. * @param bool $sensitive if TRUE value will be hidden when listing config values.
  583. *
  584. * @return bool TRUE if value was different, therefor updated in database
  585. * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
  586. * @since 29.0.0
  587. * @see IAppConfig for explanation about lazy loading
  588. */
  589. public function setValueFloat(
  590. string $app,
  591. string $key,
  592. float $value,
  593. bool $lazy = false,
  594. bool $sensitive = false
  595. ): bool {
  596. return $this->setTypedValue(
  597. $app,
  598. $key,
  599. (string)$value,
  600. $lazy,
  601. self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
  602. );
  603. }
  604. /**
  605. * @inheritDoc
  606. *
  607. * @param string $app id of the app
  608. * @param string $key config key
  609. * @param bool $value config value
  610. * @param bool $lazy set config as lazy loaded
  611. *
  612. * @return bool TRUE if value was different, therefor updated in database
  613. * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
  614. * @since 29.0.0
  615. * @see IAppConfig for explanation about lazy loading
  616. */
  617. public function setValueBool(
  618. string $app,
  619. string $key,
  620. bool $value,
  621. bool $lazy = false
  622. ): bool {
  623. return $this->setTypedValue(
  624. $app,
  625. $key,
  626. ($value) ? '1' : '0',
  627. $lazy,
  628. self::VALUE_BOOL
  629. );
  630. }
  631. /**
  632. * @inheritDoc
  633. *
  634. * @param string $app id of the app
  635. * @param string $key config key
  636. * @param array $value config value
  637. * @param bool $lazy set config as lazy loaded
  638. * @param bool $sensitive if TRUE value will be hidden when listing config values.
  639. *
  640. * @return bool TRUE if value was different, therefor updated in database
  641. * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
  642. * @throws JsonException
  643. * @since 29.0.0
  644. * @see IAppConfig for explanation about lazy loading
  645. */
  646. public function setValueArray(
  647. string $app,
  648. string $key,
  649. array $value,
  650. bool $lazy = false,
  651. bool $sensitive = false
  652. ): bool {
  653. try {
  654. return $this->setTypedValue(
  655. $app,
  656. $key,
  657. json_encode($value, JSON_THROW_ON_ERROR),
  658. $lazy,
  659. self::VALUE_ARRAY | ($sensitive ? self::VALUE_SENSITIVE : 0)
  660. );
  661. } catch (JsonException $e) {
  662. $this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
  663. throw $e;
  664. }
  665. }
  666. /**
  667. * Store a config key and its value in database
  668. *
  669. * If config key is already known with the exact same config value and same sensitive/lazy status, the
  670. * database is not updated. If config value was previously stored as sensitive, status will not be
  671. * altered.
  672. *
  673. * @param string $app id of the app
  674. * @param string $key config key
  675. * @param string $value config value
  676. * @param bool $lazy config set as lazy loaded
  677. * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
  678. *
  679. * @return bool TRUE if value was updated in database
  680. * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
  681. * @see IAppConfig for explanation about lazy loading
  682. */
  683. private function setTypedValue(
  684. string $app,
  685. string $key,
  686. string $value,
  687. bool $lazy,
  688. int $type
  689. ): bool {
  690. $this->assertParams($app, $key);
  691. $this->loadConfig($lazy);
  692. $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
  693. $inserted = $refreshCache = false;
  694. if ($sensitive || ($this->hasKey($app, $key, $lazy) && $this->isSensitive($app, $key, $lazy))) {
  695. $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
  696. }
  697. if ($this->hasKey($app, $key, $lazy)) {
  698. /**
  699. * no update if key is already known with set lazy status and value is
  700. * not different, unless sensitivity is switched from false to true.
  701. */
  702. if ($value === $this->getTypedValue($app, $key, $value, $lazy, $type)
  703. && (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
  704. return false;
  705. }
  706. } else {
  707. /**
  708. * if key is not known yet, we try to insert.
  709. * It might fail if the key exists with a different lazy flag.
  710. */
  711. try {
  712. $insert = $this->connection->getQueryBuilder();
  713. $insert->insert('appconfig')
  714. ->setValue('appid', $insert->createNamedParameter($app))
  715. ->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
  716. ->setValue('type', $insert->createNamedParameter($type, IQueryBuilder::PARAM_INT))
  717. ->setValue('configkey', $insert->createNamedParameter($key))
  718. ->setValue('configvalue', $insert->createNamedParameter($value));
  719. $insert->executeStatement();
  720. $inserted = true;
  721. } catch (DBException $e) {
  722. if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
  723. throw $e; // TODO: throw exception or just log and returns false !?
  724. }
  725. }
  726. }
  727. /**
  728. * We cannot insert a new row, meaning we need to update an already existing one
  729. */
  730. if (!$inserted) {
  731. $currType = $this->valueTypes[$app][$key] ?? 0;
  732. if ($currType === 0) { // this might happen when switching lazy loading status
  733. $this->loadConfigAll();
  734. $currType = $this->valueTypes[$app][$key] ?? 0;
  735. }
  736. /**
  737. * This should only happen during the upgrade process from 28 to 29.
  738. * We only log a warning and set it to VALUE_MIXED.
  739. */
  740. if ($currType === 0) {
  741. $this->logger->warning('Value type is set to zero (0) in database. This is fine only during the upgrade process from 28 to 29.', ['app' => $app, 'key' => $key]);
  742. $currType = self::VALUE_MIXED;
  743. }
  744. /**
  745. * we only accept a different type from the one stored in database
  746. * if the one stored in database is not-defined (VALUE_MIXED)
  747. */
  748. if (!$this->isTyped(self::VALUE_MIXED, $currType) &&
  749. ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
  750. try {
  751. $currType = $this->convertTypeToString($currType);
  752. $type = $this->convertTypeToString($type);
  753. } catch (AppConfigIncorrectTypeException) {
  754. // can be ignored, this was just needed for a better exception message.
  755. }
  756. throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
  757. }
  758. // we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
  759. if ($sensitive || $this->isTyped(self::VALUE_SENSITIVE, $currType)) {
  760. $type |= self::VALUE_SENSITIVE;
  761. }
  762. if ($lazy !== $this->isLazy($app, $key)) {
  763. $refreshCache = true;
  764. }
  765. $update = $this->connection->getQueryBuilder();
  766. $update->update('appconfig')
  767. ->set('configvalue', $update->createNamedParameter($value))
  768. ->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
  769. ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
  770. ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
  771. ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
  772. $update->executeStatement();
  773. }
  774. if ($refreshCache) {
  775. $this->clearCache();
  776. return true;
  777. }
  778. // update local cache
  779. if ($lazy) {
  780. $cache = &$this->lazyCache;
  781. } else {
  782. $cache = &$this->fastCache;
  783. }
  784. $cache[$app][$key] = $value;
  785. $this->valueTypes[$app][$key] = $type;
  786. return true;
  787. }
  788. /**
  789. * Change the type of config value.
  790. *
  791. * **WARNING:** Method is internal and **MUST** not be used as it may break things.
  792. *
  793. * @param string $app id of the app
  794. * @param string $key config key
  795. * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
  796. *
  797. * @return bool TRUE if database update were necessary
  798. * @throws AppConfigUnknownKeyException if $key is now known in database
  799. * @throws AppConfigIncorrectTypeException if $type is not valid
  800. * @internal
  801. * @since 29.0.0
  802. */
  803. public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
  804. $this->assertParams($app, $key);
  805. $this->loadConfigAll();
  806. $lazy = $this->isLazy($app, $key);
  807. // type can only be one type
  808. if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
  809. throw new AppConfigIncorrectTypeException('Unknown value type');
  810. }
  811. $currType = $this->valueTypes[$app][$key];
  812. if (($type | self::VALUE_SENSITIVE) === ($currType | self::VALUE_SENSITIVE)) {
  813. return false;
  814. }
  815. // we complete with sensitive flag if the stored value is set as sensitive
  816. if ($this->isTyped(self::VALUE_SENSITIVE, $currType)) {
  817. $type = $type | self::VALUE_SENSITIVE;
  818. }
  819. $update = $this->connection->getQueryBuilder();
  820. $update->update('appconfig')
  821. ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
  822. ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
  823. ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
  824. $update->executeStatement();
  825. $this->valueTypes[$app][$key] = $type;
  826. return true;
  827. }
  828. /**
  829. * @inheritDoc
  830. *
  831. * @param string $app id of the app
  832. * @param string $key config key
  833. * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
  834. *
  835. * @return bool TRUE if entry was found in database and an update was necessary
  836. * @since 29.0.0
  837. */
  838. public function updateSensitive(string $app, string $key, bool $sensitive): bool {
  839. $this->assertParams($app, $key);
  840. $this->loadConfigAll();
  841. try {
  842. if ($sensitive === $this->isSensitive($app, $key, null)) {
  843. return false;
  844. }
  845. } catch (AppConfigUnknownKeyException $e) {
  846. return false;
  847. }
  848. $lazy = $this->isLazy($app, $key);
  849. if ($lazy) {
  850. $cache = $this->lazyCache;
  851. } else {
  852. $cache = $this->fastCache;
  853. }
  854. if (!isset($cache[$app][$key])) {
  855. throw new AppConfigUnknownKeyException('unknown config key');
  856. }
  857. /**
  858. * type returned by getValueType() is already cleaned from sensitive flag
  859. * we just need to update it based on $sensitive and store it in database
  860. */
  861. $type = $this->getValueType($app, $key);
  862. $value = $cache[$app][$key];
  863. if ($sensitive) {
  864. $type |= self::VALUE_SENSITIVE;
  865. $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
  866. } else {
  867. $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
  868. }
  869. $update = $this->connection->getQueryBuilder();
  870. $update->update('appconfig')
  871. ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
  872. ->set('configvalue', $update->createNamedParameter($value))
  873. ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
  874. ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
  875. $update->executeStatement();
  876. $this->valueTypes[$app][$key] = $type;
  877. return true;
  878. }
  879. /**
  880. * @inheritDoc
  881. *
  882. * @param string $app id of the app
  883. * @param string $key config key
  884. * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
  885. *
  886. * @return bool TRUE if entry was found in database and an update was necessary
  887. * @since 29.0.0
  888. */
  889. public function updateLazy(string $app, string $key, bool $lazy): bool {
  890. $this->assertParams($app, $key);
  891. $this->loadConfigAll();
  892. try {
  893. if ($lazy === $this->isLazy($app, $key)) {
  894. return false;
  895. }
  896. } catch (AppConfigUnknownKeyException $e) {
  897. return false;
  898. }
  899. $update = $this->connection->getQueryBuilder();
  900. $update->update('appconfig')
  901. ->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
  902. ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
  903. ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
  904. $update->executeStatement();
  905. // At this point, it is a lot safer to clean cache
  906. $this->clearCache();
  907. return true;
  908. }
  909. /**
  910. * @inheritDoc
  911. *
  912. * @param string $app id of the app
  913. * @param string $key config key
  914. *
  915. * @return array
  916. * @throws AppConfigUnknownKeyException if config key is not known in database
  917. * @since 29.0.0
  918. */
  919. public function getDetails(string $app, string $key): array {
  920. $this->assertParams($app, $key);
  921. $this->loadConfigAll();
  922. $lazy = $this->isLazy($app, $key);
  923. if ($lazy) {
  924. $cache = $this->lazyCache;
  925. } else {
  926. $cache = $this->fastCache;
  927. }
  928. $type = $this->getValueType($app, $key);
  929. try {
  930. $typeString = $this->convertTypeToString($type);
  931. } catch (AppConfigIncorrectTypeException $e) {
  932. $this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
  933. $typeString = (string)$type;
  934. }
  935. if (!isset($cache[$app][$key])) {
  936. throw new AppConfigUnknownKeyException('unknown config key');
  937. }
  938. return [
  939. 'app' => $app,
  940. 'key' => $key,
  941. 'value' => $cache[$app][$key],
  942. 'type' => $type,
  943. 'lazy' => $lazy,
  944. 'typeString' => $typeString,
  945. 'sensitive' => $this->isSensitive($app, $key, null)
  946. ];
  947. }
  948. /**
  949. * @param string $type
  950. *
  951. * @return int
  952. * @throws AppConfigIncorrectTypeException
  953. * @since 29.0.0
  954. */
  955. public function convertTypeToInt(string $type): int {
  956. return match (strtolower($type)) {
  957. 'mixed' => IAppConfig::VALUE_MIXED,
  958. 'string' => IAppConfig::VALUE_STRING,
  959. 'integer' => IAppConfig::VALUE_INT,
  960. 'float' => IAppConfig::VALUE_FLOAT,
  961. 'boolean' => IAppConfig::VALUE_BOOL,
  962. 'array' => IAppConfig::VALUE_ARRAY,
  963. default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
  964. };
  965. }
  966. /**
  967. * @param int $type
  968. *
  969. * @return string
  970. * @throws AppConfigIncorrectTypeException
  971. * @since 29.0.0
  972. */
  973. public function convertTypeToString(int $type): string {
  974. $type &= ~self::VALUE_SENSITIVE;
  975. return match ($type) {
  976. IAppConfig::VALUE_MIXED => 'mixed',
  977. IAppConfig::VALUE_STRING => 'string',
  978. IAppConfig::VALUE_INT => 'integer',
  979. IAppConfig::VALUE_FLOAT => 'float',
  980. IAppConfig::VALUE_BOOL => 'boolean',
  981. IAppConfig::VALUE_ARRAY => 'array',
  982. default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
  983. };
  984. }
  985. /**
  986. * @inheritDoc
  987. *
  988. * @param string $app id of the app
  989. * @param string $key config key
  990. *
  991. * @since 29.0.0
  992. */
  993. public function deleteKey(string $app, string $key): void {
  994. $this->assertParams($app, $key);
  995. $qb = $this->connection->getQueryBuilder();
  996. $qb->delete('appconfig')
  997. ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
  998. ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
  999. $qb->executeStatement();
  1000. unset($this->lazyCache[$app][$key]);
  1001. unset($this->fastCache[$app][$key]);
  1002. }
  1003. /**
  1004. * @inheritDoc
  1005. *
  1006. * @param string $app id of the app
  1007. *
  1008. * @since 29.0.0
  1009. */
  1010. public function deleteApp(string $app): void {
  1011. $this->assertParams($app);
  1012. $qb = $this->connection->getQueryBuilder();
  1013. $qb->delete('appconfig')
  1014. ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
  1015. $qb->executeStatement();
  1016. $this->clearCache();
  1017. }
  1018. /**
  1019. * @inheritDoc
  1020. *
  1021. * @param bool $reload set to TRUE to refill cache instantly after clearing it
  1022. *
  1023. * @since 29.0.0
  1024. */
  1025. public function clearCache(bool $reload = false): void {
  1026. $this->lazyLoaded = $this->fastLoaded = false;
  1027. $this->lazyCache = $this->fastCache = $this->valueTypes = [];
  1028. if (!$reload) {
  1029. return;
  1030. }
  1031. $this->loadConfigAll();
  1032. }
  1033. /**
  1034. * For debug purpose.
  1035. * Returns the cached data.
  1036. *
  1037. * @return array
  1038. * @since 29.0.0
  1039. * @internal
  1040. */
  1041. public function statusCache(): array {
  1042. return [
  1043. 'fastLoaded' => $this->fastLoaded,
  1044. 'fastCache' => $this->fastCache,
  1045. 'lazyLoaded' => $this->lazyLoaded,
  1046. 'lazyCache' => $this->lazyCache,
  1047. ];
  1048. }
  1049. /**
  1050. * @param int $needle bitflag to search
  1051. * @param int $type known value
  1052. *
  1053. * @return bool TRUE if bitflag $needle is set in $type
  1054. */
  1055. private function isTyped(int $needle, int $type): bool {
  1056. return (($needle & $type) !== 0);
  1057. }
  1058. /**
  1059. * Confirm the string set for app and key fit the database description
  1060. *
  1061. * @param string $app assert $app fit in database
  1062. * @param string $configKey assert config key fit in database
  1063. * @param bool $allowEmptyApp $app can be empty string
  1064. * @param int $valueType assert value type is only one type
  1065. *
  1066. * @throws InvalidArgumentException
  1067. */
  1068. private function assertParams(string $app = '', string $configKey = '', bool $allowEmptyApp = false, int $valueType = -1): void {
  1069. if (!$allowEmptyApp && $app === '') {
  1070. throw new InvalidArgumentException('app cannot be an empty string');
  1071. }
  1072. if (strlen($app) > self::APP_MAX_LENGTH) {
  1073. throw new InvalidArgumentException(
  1074. 'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
  1075. );
  1076. }
  1077. if (strlen($configKey) > self::KEY_MAX_LENGTH) {
  1078. throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
  1079. }
  1080. if ($valueType > -1) {
  1081. $valueType &= ~self::VALUE_SENSITIVE;
  1082. if (!in_array($valueType, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
  1083. throw new InvalidArgumentException('Unknown value type');
  1084. }
  1085. }
  1086. }
  1087. private function loadConfigAll(): void {
  1088. $this->loadConfig(null);
  1089. }
  1090. /**
  1091. * Load normal config or config set as lazy loaded
  1092. *
  1093. * @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
  1094. */
  1095. private function loadConfig(?bool $lazy = false): void {
  1096. if ($this->isLoaded($lazy)) {
  1097. return;
  1098. }
  1099. if (($lazy ?? true) !== false) { // if lazy is null or true, we debug log
  1100. $this->logger->debug('The loading of lazy AppConfig values have been requested', ['exception' => new \RuntimeException('ignorable exception')]);
  1101. }
  1102. $qb = $this->connection->getQueryBuilder();
  1103. $qb->from('appconfig');
  1104. /**
  1105. * The use of $this->>migrationCompleted is only needed to manage the
  1106. * database during the upgrading process to nc29.
  1107. */
  1108. if (!$this->migrationCompleted) {
  1109. $qb->select('appid', 'configkey', 'configvalue');
  1110. } else {
  1111. // we only need value from lazy when loadConfig does not specify it
  1112. $qb->select('appid', 'configkey', 'configvalue', 'type');
  1113. if ($lazy !== null) {
  1114. $qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
  1115. } else {
  1116. $qb->addSelect('lazy');
  1117. }
  1118. }
  1119. try {
  1120. $result = $qb->executeQuery();
  1121. } catch (DBException $e) {
  1122. /**
  1123. * in case of issue with field name, it means that migration is not completed.
  1124. * Falling back to a request without select on lazy.
  1125. * This whole try/catch and the migrationCompleted variable can be removed in NC30.
  1126. */
  1127. if ($e->getReason() !== DBException::REASON_INVALID_FIELD_NAME) {
  1128. throw $e;
  1129. }
  1130. $this->migrationCompleted = false;
  1131. $this->loadConfig($lazy);
  1132. return;
  1133. }
  1134. $rows = $result->fetchAll();
  1135. foreach ($rows as $row) {
  1136. // most of the time, 'lazy' is not in the select because its value is already known
  1137. if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
  1138. $cache = &$this->lazyCache;
  1139. } else {
  1140. $cache = &$this->fastCache;
  1141. }
  1142. $cache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
  1143. $this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
  1144. }
  1145. $result->closeCursor();
  1146. $this->setAsLoaded($lazy);
  1147. }
  1148. /**
  1149. * if $lazy is:
  1150. * - false: will returns true if fast config is loaded
  1151. * - true : will returns true if lazy config is loaded
  1152. * - null : will returns true if both config are loaded
  1153. *
  1154. * @param bool $lazy
  1155. *
  1156. * @return bool
  1157. */
  1158. private function isLoaded(?bool $lazy): bool {
  1159. if ($lazy === null) {
  1160. return $this->lazyLoaded && $this->fastLoaded;
  1161. }
  1162. return $lazy ? $this->lazyLoaded : $this->fastLoaded;
  1163. }
  1164. /**
  1165. * if $lazy is:
  1166. * - false: set fast config as loaded
  1167. * - true : set lazy config as loaded
  1168. * - null : set both config as loaded
  1169. *
  1170. * @param bool $lazy
  1171. */
  1172. private function setAsLoaded(?bool $lazy): void {
  1173. if ($lazy === null) {
  1174. $this->fastLoaded = true;
  1175. $this->lazyLoaded = true;
  1176. return;
  1177. }
  1178. if ($lazy) {
  1179. $this->lazyLoaded = true;
  1180. } else {
  1181. $this->fastLoaded = true;
  1182. }
  1183. }
  1184. /**
  1185. * Gets the config value
  1186. *
  1187. * @param string $app app
  1188. * @param string $key key
  1189. * @param string $default = null, default value if the key does not exist
  1190. *
  1191. * @return string the value or $default
  1192. * @deprecated - use getValue*()
  1193. *
  1194. * This function gets a value from the appconfig table. If the key does
  1195. * not exist the default value will be returned
  1196. */
  1197. public function getValue($app, $key, $default = null) {
  1198. $this->loadConfig();
  1199. return $this->fastCache[$app][$key] ?? $default;
  1200. }
  1201. /**
  1202. * Sets a value. If the key did not exist before it will be created.
  1203. *
  1204. * @param string $app app
  1205. * @param string $key key
  1206. * @param string|float|int $value value
  1207. *
  1208. * @return bool True if the value was inserted or updated, false if the value was the same
  1209. * @throws AppConfigTypeConflictException
  1210. * @throws AppConfigUnknownKeyException
  1211. * @deprecated
  1212. */
  1213. public function setValue($app, $key, $value) {
  1214. /**
  1215. * TODO: would it be overkill, or decently improve performance, to catch
  1216. * call to this method with $key='enabled' and 'hide' config value related
  1217. * to $app when the app is disabled (by modifying entry in database: lazy=lazy+2)
  1218. * or enabled (lazy=lazy-2)
  1219. *
  1220. * this solution would remove the loading of config values from disabled app
  1221. * unless calling the method {@see loadConfigAll()}
  1222. */
  1223. return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
  1224. }
  1225. /**
  1226. * get multiple values, either the app or key can be used as wildcard by setting it to false
  1227. *
  1228. * @param string|false $app
  1229. * @param string|false $key
  1230. *
  1231. * @return array|false
  1232. * @deprecated 29.0.0 use {@see getAllValues()}
  1233. */
  1234. public function getValues($app, $key) {
  1235. if (($app !== false) === ($key !== false)) {
  1236. return false;
  1237. }
  1238. $key = ($key === false) ? '' : $key;
  1239. if (!$app) {
  1240. return $this->searchValues($key);
  1241. } else {
  1242. return $this->getAllValues($app, $key);
  1243. }
  1244. }
  1245. /**
  1246. * get all values of the app or and filters out sensitive data
  1247. *
  1248. * @param string $app
  1249. *
  1250. * @return array
  1251. * @deprecated 29.0.0 use {@see getAllValues()}
  1252. */
  1253. public function getFilteredValues($app) {
  1254. return $this->getAllValues($app, filtered: true);
  1255. }
  1256. /**
  1257. * @param string $app
  1258. *
  1259. * @return string[]
  1260. * @deprecated data sensitivity should be set when calling setValue*()
  1261. */
  1262. private function getSensitiveKeys(string $app): array {
  1263. $sensitiveValues = [
  1264. 'circles' => [
  1265. '/^key_pairs$/',
  1266. '/^local_gskey$/',
  1267. ],
  1268. 'external' => [
  1269. '/^sites$/',
  1270. ],
  1271. 'integration_discourse' => [
  1272. '/^private_key$/',
  1273. '/^public_key$/',
  1274. ],
  1275. 'integration_dropbox' => [
  1276. '/^client_id$/',
  1277. '/^client_secret$/',
  1278. ],
  1279. 'integration_github' => [
  1280. '/^client_id$/',
  1281. '/^client_secret$/',
  1282. ],
  1283. 'integration_gitlab' => [
  1284. '/^client_id$/',
  1285. '/^client_secret$/',
  1286. '/^oauth_instance_url$/',
  1287. ],
  1288. 'integration_google' => [
  1289. '/^client_id$/',
  1290. '/^client_secret$/',
  1291. ],
  1292. 'integration_jira' => [
  1293. '/^client_id$/',
  1294. '/^client_secret$/',
  1295. '/^forced_instance_url$/',
  1296. ],
  1297. 'integration_onedrive' => [
  1298. '/^client_id$/',
  1299. '/^client_secret$/',
  1300. ],
  1301. 'integration_openproject' => [
  1302. '/^client_id$/',
  1303. '/^client_secret$/',
  1304. '/^oauth_instance_url$/',
  1305. ],
  1306. 'integration_reddit' => [
  1307. '/^client_id$/',
  1308. '/^client_secret$/',
  1309. ],
  1310. 'integration_suitecrm' => [
  1311. '/^client_id$/',
  1312. '/^client_secret$/',
  1313. '/^oauth_instance_url$/',
  1314. ],
  1315. 'integration_twitter' => [
  1316. '/^consumer_key$/',
  1317. '/^consumer_secret$/',
  1318. '/^followed_user$/',
  1319. ],
  1320. 'integration_zammad' => [
  1321. '/^client_id$/',
  1322. '/^client_secret$/',
  1323. '/^oauth_instance_url$/',
  1324. ],
  1325. 'notify_push' => [
  1326. '/^cookie$/',
  1327. ],
  1328. 'serverinfo' => [
  1329. '/^token$/',
  1330. ],
  1331. 'spreed' => [
  1332. '/^bridge_bot_password$/',
  1333. '/^hosted-signaling-server-(.*)$/',
  1334. '/^recording_servers$/',
  1335. '/^signaling_servers$/',
  1336. '/^signaling_ticket_secret$/',
  1337. '/^signaling_token_privkey_(.*)$/',
  1338. '/^signaling_token_pubkey_(.*)$/',
  1339. '/^sip_bridge_dialin_info$/',
  1340. '/^sip_bridge_shared_secret$/',
  1341. '/^stun_servers$/',
  1342. '/^turn_servers$/',
  1343. '/^turn_server_secret$/',
  1344. ],
  1345. 'support' => [
  1346. '/^last_response$/',
  1347. '/^potential_subscription_key$/',
  1348. '/^subscription_key$/',
  1349. ],
  1350. 'theming' => [
  1351. '/^imprintUrl$/',
  1352. '/^privacyUrl$/',
  1353. '/^slogan$/',
  1354. '/^url$/',
  1355. ],
  1356. 'user_ldap' => [
  1357. '/^(s..)?ldap_agent_password$/',
  1358. ],
  1359. 'user_saml' => [
  1360. '/^idp-x509cert$/',
  1361. ],
  1362. ];
  1363. return $sensitiveValues[$app] ?? [];
  1364. }
  1365. /**
  1366. * Clear all the cached app config values
  1367. * New cache will be generated next time a config value is retrieved
  1368. *
  1369. * @deprecated use {@see clearCache()}
  1370. */
  1371. public function clearCachedConfig(): void {
  1372. $this->clearCache();
  1373. }
  1374. }