AppConfig.php 42 KB

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