Completed
Push — master ( dc48b6...a1f4b5 )
by
unknown
37:52 queued 15s
created
tests/lib/AppConfigTest.php 2 patches
Indentation   +1474 added lines, -1474 removed lines patch added patch discarded remove patch
@@ -26,1478 +26,1478 @@
 block discarded – undo
26 26
  * @package Test
27 27
  */
28 28
 class AppConfigTest extends TestCase {
29
-	protected IAppConfig $appConfig;
30
-	protected IDBConnection $connection;
31
-	private IConfig $config;
32
-	private LoggerInterface $logger;
33
-	private ICrypto $crypto;
34
-
35
-	private array $originalConfig;
36
-
37
-	/**
38
-	 * @var array<string, array<string, array<string, string, int, bool, bool>>>
39
-	 *                                                                           [appId => [configKey, configValue, valueType, lazy, sensitive]]
40
-	 */
41
-	private static array $baseStruct
42
-		= [
43
-			'testapp' => [
44
-				'enabled' => ['enabled', 'yes'],
45
-				'installed_version' => ['installed_version', '1.2.3'],
46
-				'depends_on' => ['depends_on', 'someapp'],
47
-				'deletethis' => ['deletethis', 'deletethis'],
48
-				'key' => ['key', 'value']
49
-			],
50
-			'searchtest' => [
51
-				'search_key1' => ['search_key1', 'key1', IAppConfig::VALUE_STRING],
52
-				'search_key2' => ['search_key2', 'key2', IAppConfig::VALUE_STRING],
53
-				'search_key3' => ['search_key3', 'key3', IAppConfig::VALUE_STRING],
54
-				'searchnot_key4' => ['searchnot_key4', 'key4', IAppConfig::VALUE_STRING],
55
-				'search_key5_lazy' => ['search_key5_lazy', 'key5', IAppConfig::VALUE_STRING, true],
56
-			],
57
-			'someapp' => [
58
-				'key' => ['key', 'value'],
59
-				'otherkey' => ['otherkey', 'othervalue']
60
-			],
61
-			'123456' => [
62
-				'enabled' => ['enabled', 'yes'],
63
-				'key' => ['key', 'value']
64
-			],
65
-			'anotherapp' => [
66
-				'enabled' => ['enabled', 'no'],
67
-				'installed_version' => ['installed_version', '3.2.1'],
68
-				'key' => ['key', 'value']
69
-			],
70
-			'non-sensitive-app' => [
71
-				'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, false],
72
-				'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, false],
73
-			],
74
-			'sensitive-app' => [
75
-				'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, true],
76
-				'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, true],
77
-			],
78
-			'only-lazy' => [
79
-				'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true]
80
-			],
81
-			'typed' => [
82
-				'mixed' => ['mixed', 'mix', IAppConfig::VALUE_MIXED],
83
-				'string' => ['string', 'value', IAppConfig::VALUE_STRING],
84
-				'int' => ['int', '42', IAppConfig::VALUE_INT],
85
-				'float' => ['float', '3.14', IAppConfig::VALUE_FLOAT],
86
-				'bool' => ['bool', '1', IAppConfig::VALUE_BOOL],
87
-				'array' => ['array', '{"test": 1}', IAppConfig::VALUE_ARRAY],
88
-			],
89
-			'prefix-app' => [
90
-				'key1' => ['key1', 'value'],
91
-				'prefix1' => ['prefix1', 'value'],
92
-				'prefix-2' => ['prefix-2', 'value'],
93
-				'key-2' => ['key-2', 'value'],
94
-			]
95
-		];
96
-
97
-	protected function setUp(): void {
98
-		parent::setUp();
99
-
100
-		$this->connection = Server::get(IDBConnection::class);
101
-		$this->config = Server::get(IConfig::class);
102
-		$this->logger = Server::get(LoggerInterface::class);
103
-		$this->crypto = Server::get(ICrypto::class);
104
-
105
-		// storing current config and emptying the data table
106
-		$sql = $this->connection->getQueryBuilder();
107
-		$sql->select('*')
108
-			->from('appconfig');
109
-		$result = $sql->executeQuery();
110
-		$this->originalConfig = $result->fetchAll();
111
-		$result->closeCursor();
112
-
113
-		$sql = $this->connection->getQueryBuilder();
114
-		$sql->delete('appconfig');
115
-		$sql->executeStatement();
116
-
117
-		$sql = $this->connection->getQueryBuilder();
118
-		$sql->insert('appconfig')
119
-			->values(
120
-				[
121
-					'appid' => $sql->createParameter('appid'),
122
-					'configkey' => $sql->createParameter('configkey'),
123
-					'configvalue' => $sql->createParameter('configvalue'),
124
-					'type' => $sql->createParameter('type'),
125
-					'lazy' => $sql->createParameter('lazy')
126
-				]
127
-			);
128
-
129
-		foreach (self::$baseStruct as $appId => $appData) {
130
-			foreach ($appData as $key => $row) {
131
-				$value = $row[1];
132
-				$type = $row[2] ?? IAppConfig::VALUE_MIXED;
133
-				if (($row[4] ?? false) === true) {
134
-					$type |= IAppConfig::VALUE_SENSITIVE;
135
-					$value = self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX') . $this->crypto->encrypt($value);
136
-					self::$baseStruct[$appId][$key]['encrypted'] = $value;
137
-				}
138
-
139
-				$sql->setParameters(
140
-					[
141
-						'appid' => $appId,
142
-						'configkey' => $row[0],
143
-						'configvalue' => $value,
144
-						'type' => $type,
145
-						'lazy' => (($row[3] ?? false) === true) ? 1 : 0
146
-					]
147
-				)->executeStatement();
148
-			}
149
-		}
150
-	}
151
-
152
-	protected function tearDown(): void {
153
-		$sql = $this->connection->getQueryBuilder();
154
-		$sql->delete('appconfig');
155
-		$sql->executeStatement();
156
-
157
-		$sql = $this->connection->getQueryBuilder();
158
-		$sql->insert('appconfig')
159
-			->values(
160
-				[
161
-					'appid' => $sql->createParameter('appid'),
162
-					'configkey' => $sql->createParameter('configkey'),
163
-					'configvalue' => $sql->createParameter('configvalue'),
164
-					'lazy' => $sql->createParameter('lazy'),
165
-					'type' => $sql->createParameter('type'),
166
-				]
167
-			);
168
-
169
-		foreach ($this->originalConfig as $key => $configs) {
170
-			$sql->setParameter('appid', $configs['appid'])
171
-				->setParameter('configkey', $configs['configkey'])
172
-				->setParameter('configvalue', $configs['configvalue'])
173
-				->setParameter('lazy', ($configs['lazy'] === '1') ? '1' : '0')
174
-				->setParameter('type', $configs['type']);
175
-			$sql->executeStatement();
176
-		}
177
-
178
-		//		$this->restoreService(AppConfig::class);
179
-		parent::tearDown();
180
-	}
181
-
182
-	/**
183
-	 * @param bool $preLoading TRUE will preload the 'fast' cache, which is the normal behavior of usual
184
-	 *                         IAppConfig
185
-	 *
186
-	 * @return IAppConfig
187
-	 */
188
-	private function generateAppConfig(bool $preLoading = true): IAppConfig {
189
-		/** @var AppConfig $config */
190
-		$config = new AppConfig(
191
-			$this->connection,
192
-			$this->config,
193
-			$this->logger,
194
-			$this->crypto,
195
-		);
196
-		$msg = ' generateAppConfig() failed to confirm cache status';
197
-
198
-		// confirm cache status
199
-		$status = $config->statusCache();
200
-		$this->assertSame(false, $status['fastLoaded'], $msg);
201
-		$this->assertSame(false, $status['lazyLoaded'], $msg);
202
-		$this->assertSame([], $status['fastCache'], $msg);
203
-		$this->assertSame([], $status['lazyCache'], $msg);
204
-		if ($preLoading) {
205
-			// simple way to initiate the load of non-lazy config values in cache
206
-			$config->getValueString('core', 'preload', '');
207
-
208
-			// confirm cache status
209
-			$status = $config->statusCache();
210
-			$this->assertSame(true, $status['fastLoaded'], $msg);
211
-			$this->assertSame(false, $status['lazyLoaded'], $msg);
212
-
213
-			$apps = array_values(array_diff(array_keys(self::$baseStruct), ['only-lazy']));
214
-			$this->assertEqualsCanonicalizing($apps, array_keys($status['fastCache']), $msg);
215
-			$this->assertSame([], array_keys($status['lazyCache']), $msg);
216
-		}
217
-
218
-		return $config;
219
-	}
220
-
221
-	public function testGetApps(): void {
222
-		$config = $this->generateAppConfig(false);
223
-
224
-		$this->assertEqualsCanonicalizing(array_keys(self::$baseStruct), $config->getApps());
225
-	}
226
-
227
-	public function testGetAppInstalledVersions(): void {
228
-		$config = $this->generateAppConfig(false);
229
-
230
-		$this->assertEquals(
231
-			['testapp' => '1.2.3', 'anotherapp' => '3.2.1'],
232
-			$config->getAppInstalledVersions(false)
233
-		);
234
-		$this->assertEquals(
235
-			['testapp' => '1.2.3'],
236
-			$config->getAppInstalledVersions(true)
237
-		);
238
-	}
239
-
240
-	/**
241
-	 * returns list of app and their keys
242
-	 *
243
-	 * @return array<string, string[]> ['appId' => ['key1', 'key2', ]]
244
-	 * @see testGetKeys
245
-	 */
246
-	public static function providerGetAppKeys(): array {
247
-		$appKeys = [];
248
-		foreach (self::$baseStruct as $appId => $appData) {
249
-			$keys = [];
250
-			foreach ($appData as $row) {
251
-				$keys[] = $row[0];
252
-			}
253
-			$appKeys[] = [(string)$appId, $keys];
254
-		}
255
-
256
-		return $appKeys;
257
-	}
258
-
259
-	/**
260
-	 * returns list of config keys
261
-	 *
262
-	 * @return array<string, string, string, int, bool, bool> [appId, key, value, type, lazy, sensitive]
263
-	 * @see testIsSensitive
264
-	 * @see testIsLazy
265
-	 * @see testGetKeys
266
-	 */
267
-	public static function providerGetKeys(): array {
268
-		$appKeys = [];
269
-		foreach (self::$baseStruct as $appId => $appData) {
270
-			foreach ($appData as $row) {
271
-				$appKeys[] = [
272
-					(string)$appId, $row[0], $row[1], $row[2] ?? IAppConfig::VALUE_MIXED, $row[3] ?? false,
273
-					$row[4] ?? false
274
-				];
275
-			}
276
-		}
277
-
278
-		return $appKeys;
279
-	}
280
-
281
-	/**
282
-	 *
283
-	 * @param string $appId
284
-	 * @param array $expectedKeys
285
-	 */
286
-	#[\PHPUnit\Framework\Attributes\DataProvider('providerGetAppKeys')]
287
-	public function testGetKeys(string $appId, array $expectedKeys): void {
288
-		$config = $this->generateAppConfig();
289
-		$this->assertEqualsCanonicalizing($expectedKeys, $config->getKeys($appId));
290
-	}
291
-
292
-	public function testGetKeysOnUnknownAppShouldReturnsEmptyArray(): void {
293
-		$config = $this->generateAppConfig();
294
-		$this->assertEqualsCanonicalizing([], $config->getKeys('unknown-app'));
295
-	}
296
-
297
-	/**
298
-	 *
299
-	 * @param string $appId
300
-	 * @param string $configKey
301
-	 * @param string $value
302
-	 * @param bool $lazy
303
-	 */
304
-	#[\PHPUnit\Framework\Attributes\DataProvider('providerGetKeys')]
305
-	public function testHasKey(string $appId, string $configKey, string $value, int $type, bool $lazy): void {
306
-		$config = $this->generateAppConfig();
307
-		$this->assertEquals(true, $config->hasKey($appId, $configKey, $lazy));
308
-	}
309
-
310
-	public function testHasKeyOnNonExistentKeyReturnsFalse(): void {
311
-		$config = $this->generateAppConfig();
312
-		$this->assertEquals(false, $config->hasKey(array_keys(self::$baseStruct)[0], 'inexistant-key'));
313
-	}
314
-
315
-	public function testHasKeyOnUnknownAppReturnsFalse(): void {
316
-		$config = $this->generateAppConfig();
317
-		$this->assertEquals(false, $config->hasKey('inexistant-app', 'inexistant-key'));
318
-	}
319
-
320
-	public function testHasKeyOnMistypedAsLazyReturnsFalse(): void {
321
-		$config = $this->generateAppConfig();
322
-		$this->assertSame(false, $config->hasKey('non-sensitive-app', 'non-lazy-key', true));
323
-	}
324
-
325
-	public function testHasKeyOnMistypeAsNonLazyReturnsFalse(): void {
326
-		$config = $this->generateAppConfig();
327
-		$this->assertSame(false, $config->hasKey('non-sensitive-app', 'lazy-key', false));
328
-	}
329
-
330
-	public function testHasKeyOnMistypeAsNonLazyReturnsTrueWithLazyArgumentIsNull(): void {
331
-		$config = $this->generateAppConfig();
332
-		$this->assertSame(true, $config->hasKey('non-sensitive-app', 'lazy-key', null));
333
-	}
334
-
335
-	#[\PHPUnit\Framework\Attributes\DataProvider('providerGetKeys')]
336
-	public function testIsSensitive(
337
-		string $appId, string $configKey, string $configValue, int $type, bool $lazy, bool $sensitive,
338
-	): void {
339
-		$config = $this->generateAppConfig();
340
-		$this->assertEquals($sensitive, $config->isSensitive($appId, $configKey, $lazy));
341
-	}
342
-
343
-	public function testIsSensitiveOnNonExistentKeyThrowsException(): void {
344
-		$config = $this->generateAppConfig();
345
-		$this->expectException(AppConfigUnknownKeyException::class);
346
-		$config->isSensitive(array_keys(self::$baseStruct)[0], 'inexistant-key');
347
-	}
348
-
349
-	public function testIsSensitiveOnUnknownAppThrowsException(): void {
350
-		$config = $this->generateAppConfig();
351
-		$this->expectException(AppConfigUnknownKeyException::class);
352
-		$config->isSensitive('unknown-app', 'inexistant-key');
353
-	}
354
-
355
-	public function testIsSensitiveOnSensitiveMistypedAsLazy(): void {
356
-		$config = $this->generateAppConfig();
357
-		$this->assertSame(true, $config->isSensitive('sensitive-app', 'non-lazy-key', true));
358
-	}
359
-
360
-	public function testIsSensitiveOnNonSensitiveMistypedAsLazy(): void {
361
-		$config = $this->generateAppConfig();
362
-		$this->assertSame(false, $config->isSensitive('non-sensitive-app', 'non-lazy-key', true));
363
-	}
364
-
365
-	public function testIsSensitiveOnSensitiveMistypedAsNonLazyThrowsException(): void {
366
-		$config = $this->generateAppConfig();
367
-		$this->expectException(AppConfigUnknownKeyException::class);
368
-		$config->isSensitive('sensitive-app', 'lazy-key', false);
369
-	}
370
-
371
-	public function testIsSensitiveOnNonSensitiveMistypedAsNonLazyThrowsException(): void {
372
-		$config = $this->generateAppConfig();
373
-		$this->expectException(AppConfigUnknownKeyException::class);
374
-		$config->isSensitive('non-sensitive-app', 'lazy-key', false);
375
-	}
376
-
377
-	#[\PHPUnit\Framework\Attributes\DataProvider('providerGetKeys')]
378
-	public function testIsLazy(string $appId, string $configKey, string $configValue, int $type, bool $lazy,
379
-	): void {
380
-		$config = $this->generateAppConfig();
381
-		$this->assertEquals($lazy, $config->isLazy($appId, $configKey));
382
-	}
383
-
384
-	public function testIsLazyOnNonExistentKeyThrowsException(): void {
385
-		$config = $this->generateAppConfig();
386
-		$this->expectException(AppConfigUnknownKeyException::class);
387
-		$config->isLazy(array_keys(self::$baseStruct)[0], 'inexistant-key');
388
-	}
389
-
390
-	public function testIsLazyOnUnknownAppThrowsException(): void {
391
-		$config = $this->generateAppConfig();
392
-		$this->expectException(AppConfigUnknownKeyException::class);
393
-		$config->isLazy('unknown-app', 'inexistant-key');
394
-	}
395
-
396
-	public function testGetAllValues(): void {
397
-		$config = $this->generateAppConfig();
398
-		$this->assertEquals(
399
-			[
400
-				'array' => ['test' => 1],
401
-				'bool' => true,
402
-				'float' => 3.14,
403
-				'int' => 42,
404
-				'mixed' => 'mix',
405
-				'string' => 'value',
406
-			],
407
-			$config->getAllValues('typed')
408
-		);
409
-	}
410
-
411
-	public function testGetAllValuesWithEmptyApp(): void {
412
-		$config = $this->generateAppConfig();
413
-		$this->expectException(InvalidArgumentException::class);
414
-		$config->getAllValues('');
415
-	}
416
-
417
-	/**
418
-	 *
419
-	 * @param string $appId
420
-	 * @param array $keys
421
-	 */
422
-	#[\PHPUnit\Framework\Attributes\DataProvider('providerGetAppKeys')]
423
-	public function testGetAllValuesWithEmptyKey(string $appId, array $keys): void {
424
-		$config = $this->generateAppConfig();
425
-		$this->assertEqualsCanonicalizing($keys, array_keys($config->getAllValues($appId, '')));
426
-	}
427
-
428
-	public function testGetAllValuesWithPrefix(): void {
429
-		$config = $this->generateAppConfig();
430
-		$this->assertEqualsCanonicalizing(['prefix1', 'prefix-2'], array_keys($config->getAllValues('prefix-app', 'prefix')));
431
-	}
432
-
433
-	public function testSearchValues(): void {
434
-		$config = $this->generateAppConfig();
435
-		$this->assertEqualsCanonicalizing(['testapp' => 'yes', '123456' => 'yes', 'anotherapp' => 'no'], $config->searchValues('enabled'));
436
-	}
437
-
438
-	public function testGetValueString(): void {
439
-		$config = $this->generateAppConfig();
440
-		$this->assertSame('value', $config->getValueString('typed', 'string', ''));
441
-	}
442
-
443
-	public function testGetValueStringOnUnknownAppReturnsDefault(): void {
444
-		$config = $this->generateAppConfig();
445
-		$this->assertSame('default-1', $config->getValueString('typed-1', 'string', 'default-1'));
446
-	}
447
-
448
-	public function testGetValueStringOnNonExistentKeyReturnsDefault(): void {
449
-		$config = $this->generateAppConfig();
450
-		$this->assertSame('default-2', $config->getValueString('typed', 'string-2', 'default-2'));
451
-	}
452
-
453
-	public function testGetValueStringOnWrongType(): void {
454
-		$config = $this->generateAppConfig();
455
-		$this->expectException(AppConfigTypeConflictException::class);
456
-		$config->getValueString('typed', 'int');
457
-	}
458
-
459
-	public function testGetNonLazyValueStringAsLazy(): void {
460
-		$config = $this->generateAppConfig();
461
-		$this->assertSame('value', $config->getValueString('non-sensitive-app', 'non-lazy-key', 'default', lazy: true));
462
-	}
463
-
464
-	public function testGetValueInt(): void {
465
-		$config = $this->generateAppConfig();
466
-		$this->assertSame(42, $config->getValueInt('typed', 'int', 0));
467
-	}
468
-
469
-	public function testGetValueIntOnUnknownAppReturnsDefault(): void {
470
-		$config = $this->generateAppConfig();
471
-		$this->assertSame(1, $config->getValueInt('typed-1', 'int', 1));
472
-	}
473
-
474
-	public function testGetValueIntOnNonExistentKeyReturnsDefault(): void {
475
-		$config = $this->generateAppConfig();
476
-		$this->assertSame(2, $config->getValueInt('typed', 'int-2', 2));
477
-	}
478
-
479
-	public function testGetValueIntOnWrongType(): void {
480
-		$config = $this->generateAppConfig();
481
-		$this->expectException(AppConfigTypeConflictException::class);
482
-		$config->getValueInt('typed', 'float');
483
-	}
484
-
485
-	public function testGetValueFloat(): void {
486
-		$config = $this->generateAppConfig();
487
-		$this->assertSame(3.14, $config->getValueFloat('typed', 'float', 0));
488
-	}
489
-
490
-	public function testGetValueFloatOnNonUnknownAppReturnsDefault(): void {
491
-		$config = $this->generateAppConfig();
492
-		$this->assertSame(1.11, $config->getValueFloat('typed-1', 'float', 1.11));
493
-	}
494
-
495
-	public function testGetValueFloatOnNonExistentKeyReturnsDefault(): void {
496
-		$config = $this->generateAppConfig();
497
-		$this->assertSame(2.22, $config->getValueFloat('typed', 'float-2', 2.22));
498
-	}
499
-
500
-	public function testGetValueFloatOnWrongType(): void {
501
-		$config = $this->generateAppConfig();
502
-		$this->expectException(AppConfigTypeConflictException::class);
503
-		$config->getValueFloat('typed', 'bool');
504
-	}
505
-
506
-	public function testGetValueBool(): void {
507
-		$config = $this->generateAppConfig();
508
-		$this->assertSame(true, $config->getValueBool('typed', 'bool'));
509
-	}
510
-
511
-	public function testGetValueBoolOnUnknownAppReturnsDefault(): void {
512
-		$config = $this->generateAppConfig();
513
-		$this->assertSame(false, $config->getValueBool('typed-1', 'bool', false));
514
-	}
515
-
516
-	public function testGetValueBoolOnNonExistentKeyReturnsDefault(): void {
517
-		$config = $this->generateAppConfig();
518
-		$this->assertSame(false, $config->getValueBool('typed', 'bool-2'));
519
-	}
520
-
521
-	public function testGetValueBoolOnWrongType(): void {
522
-		$config = $this->generateAppConfig();
523
-		$this->expectException(AppConfigTypeConflictException::class);
524
-		$config->getValueBool('typed', 'array');
525
-	}
526
-
527
-	public function testGetValueArray(): void {
528
-		$config = $this->generateAppConfig();
529
-		$this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('typed', 'array', []));
530
-	}
531
-
532
-	public function testGetValueArrayOnUnknownAppReturnsDefault(): void {
533
-		$config = $this->generateAppConfig();
534
-		$this->assertSame([1], $config->getValueArray('typed-1', 'array', [1]));
535
-	}
536
-
537
-	public function testGetValueArrayOnNonExistentKeyReturnsDefault(): void {
538
-		$config = $this->generateAppConfig();
539
-		$this->assertSame([1, 2], $config->getValueArray('typed', 'array-2', [1, 2]));
540
-	}
541
-
542
-	public function testGetValueArrayOnWrongType(): void {
543
-		$config = $this->generateAppConfig();
544
-		$this->expectException(AppConfigTypeConflictException::class);
545
-		$config->getValueArray('typed', 'string');
546
-	}
547
-
548
-
549
-	/**
550
-	 * @return array
551
-	 * @see testGetValueType
552
-	 *
553
-	 * @see testGetValueMixed
554
-	 */
555
-	public static function providerGetValueMixed(): array {
556
-		return [
557
-			// key, value, type
558
-			['mixed', 'mix', IAppConfig::VALUE_MIXED],
559
-			['string', 'value', IAppConfig::VALUE_STRING],
560
-			['int', '42', IAppConfig::VALUE_INT],
561
-			['float', '3.14', IAppConfig::VALUE_FLOAT],
562
-			['bool', '1', IAppConfig::VALUE_BOOL],
563
-			['array', '{"test": 1}', IAppConfig::VALUE_ARRAY],
564
-		];
565
-	}
566
-
567
-	/**
568
-	 *
569
-	 * @param string $key
570
-	 * @param string $value
571
-	 */
572
-	#[\PHPUnit\Framework\Attributes\DataProvider('providerGetValueMixed')]
573
-	public function testGetValueMixed(string $key, string $value): void {
574
-		$config = $this->generateAppConfig();
575
-		$this->assertSame($value, $config->getValueMixed('typed', $key));
576
-	}
577
-
578
-	/**
579
-	 *
580
-	 * @param string $key
581
-	 * @param string $value
582
-	 * @param int $type
583
-	 */
584
-	#[\PHPUnit\Framework\Attributes\DataProvider('providerGetValueMixed')]
585
-	public function testGetValueType(string $key, string $value, int $type): void {
586
-		$config = $this->generateAppConfig();
587
-		$this->assertSame($type, $config->getValueType('typed', $key));
588
-	}
589
-
590
-	public function testGetValueTypeOnUnknownApp(): void {
591
-		$config = $this->generateAppConfig();
592
-		$this->expectException(AppConfigUnknownKeyException::class);
593
-		$config->getValueType('typed-1', 'string');
594
-	}
595
-
596
-	public function testGetValueTypeOnNonExistentKey(): void {
597
-		$config = $this->generateAppConfig();
598
-		$this->expectException(AppConfigUnknownKeyException::class);
599
-		$config->getValueType('typed', 'string-2');
600
-	}
601
-
602
-	public function testSetValueString(): void {
603
-		$config = $this->generateAppConfig();
604
-		$config->setValueString('feed', 'string', 'value-1');
605
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
606
-	}
607
-
608
-	public function testSetValueStringCache(): void {
609
-		$config = $this->generateAppConfig();
610
-		$config->setValueString('feed', 'string', 'value-1');
611
-		$status = $config->statusCache();
612
-		$this->assertSame('value-1', $status['fastCache']['feed']['string']);
613
-	}
614
-
615
-	public function testSetValueStringDatabase(): void {
616
-		$config = $this->generateAppConfig();
617
-		$config->setValueString('feed', 'string', 'value-1');
618
-		$config->clearCache();
619
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
620
-	}
621
-
622
-	public function testSetValueStringIsUpdated(): void {
623
-		$config = $this->generateAppConfig();
624
-		$config->setValueString('feed', 'string', 'value-1');
625
-		$this->assertSame(true, $config->setValueString('feed', 'string', 'value-2'));
626
-	}
627
-
628
-	public function testSetValueStringIsNotUpdated(): void {
629
-		$config = $this->generateAppConfig();
630
-		$config->setValueString('feed', 'string', 'value-1');
631
-		$this->assertSame(false, $config->setValueString('feed', 'string', 'value-1'));
632
-	}
633
-
634
-	public function testSetValueStringIsUpdatedCache(): void {
635
-		$config = $this->generateAppConfig();
636
-		$config->setValueString('feed', 'string', 'value-1');
637
-		$config->setValueString('feed', 'string', 'value-2');
638
-		$status = $config->statusCache();
639
-		$this->assertSame('value-2', $status['fastCache']['feed']['string']);
640
-	}
641
-
642
-	public function testSetValueStringIsUpdatedDatabase(): void {
643
-		$config = $this->generateAppConfig();
644
-		$config->setValueString('feed', 'string', 'value-1');
645
-		$config->setValueString('feed', 'string', 'value-2');
646
-		$config->clearCache();
647
-		$this->assertSame('value-2', $config->getValueString('feed', 'string', ''));
648
-	}
649
-
650
-	public function testSetValueInt(): void {
651
-		$config = $this->generateAppConfig();
652
-		$config->setValueInt('feed', 'int', 42);
653
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
654
-	}
655
-
656
-	public function testSetValueIntCache(): void {
657
-		$config = $this->generateAppConfig();
658
-		$config->setValueInt('feed', 'int', 42);
659
-		$status = $config->statusCache();
660
-		$this->assertSame('42', $status['fastCache']['feed']['int']);
661
-	}
662
-
663
-	public function testSetValueIntDatabase(): void {
664
-		$config = $this->generateAppConfig();
665
-		$config->setValueInt('feed', 'int', 42);
666
-		$config->clearCache();
667
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
668
-	}
669
-
670
-	public function testSetValueIntIsUpdated(): void {
671
-		$config = $this->generateAppConfig();
672
-		$config->setValueInt('feed', 'int', 42);
673
-		$this->assertSame(true, $config->setValueInt('feed', 'int', 17));
674
-	}
675
-
676
-	public function testSetValueIntIsNotUpdated(): void {
677
-		$config = $this->generateAppConfig();
678
-		$config->setValueInt('feed', 'int', 42);
679
-		$this->assertSame(false, $config->setValueInt('feed', 'int', 42));
680
-	}
681
-
682
-	public function testSetValueIntIsUpdatedCache(): void {
683
-		$config = $this->generateAppConfig();
684
-		$config->setValueInt('feed', 'int', 42);
685
-		$config->setValueInt('feed', 'int', 17);
686
-		$status = $config->statusCache();
687
-		$this->assertSame('17', $status['fastCache']['feed']['int']);
688
-	}
689
-
690
-	public function testSetValueIntIsUpdatedDatabase(): void {
691
-		$config = $this->generateAppConfig();
692
-		$config->setValueInt('feed', 'int', 42);
693
-		$config->setValueInt('feed', 'int', 17);
694
-		$config->clearCache();
695
-		$this->assertSame(17, $config->getValueInt('feed', 'int', 0));
696
-	}
697
-
698
-	public function testSetValueFloat(): void {
699
-		$config = $this->generateAppConfig();
700
-		$config->setValueFloat('feed', 'float', 3.14);
701
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
702
-	}
703
-
704
-	public function testSetValueFloatCache(): void {
705
-		$config = $this->generateAppConfig();
706
-		$config->setValueFloat('feed', 'float', 3.14);
707
-		$status = $config->statusCache();
708
-		$this->assertSame('3.14', $status['fastCache']['feed']['float']);
709
-	}
710
-
711
-	public function testSetValueFloatDatabase(): void {
712
-		$config = $this->generateAppConfig();
713
-		$config->setValueFloat('feed', 'float', 3.14);
714
-		$config->clearCache();
715
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
716
-	}
717
-
718
-	public function testSetValueFloatIsUpdated(): void {
719
-		$config = $this->generateAppConfig();
720
-		$config->setValueFloat('feed', 'float', 3.14);
721
-		$this->assertSame(true, $config->setValueFloat('feed', 'float', 1.23));
722
-	}
723
-
724
-	public function testSetValueFloatIsNotUpdated(): void {
725
-		$config = $this->generateAppConfig();
726
-		$config->setValueFloat('feed', 'float', 3.14);
727
-		$this->assertSame(false, $config->setValueFloat('feed', 'float', 3.14));
728
-	}
729
-
730
-	public function testSetValueFloatIsUpdatedCache(): void {
731
-		$config = $this->generateAppConfig();
732
-		$config->setValueFloat('feed', 'float', 3.14);
733
-		$config->setValueFloat('feed', 'float', 1.23);
734
-		$status = $config->statusCache();
735
-		$this->assertSame('1.23', $status['fastCache']['feed']['float']);
736
-	}
737
-
738
-	public function testSetValueFloatIsUpdatedDatabase(): void {
739
-		$config = $this->generateAppConfig();
740
-		$config->setValueFloat('feed', 'float', 3.14);
741
-		$config->setValueFloat('feed', 'float', 1.23);
742
-		$config->clearCache();
743
-		$this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0));
744
-	}
745
-
746
-	public function testSetValueBool(): void {
747
-		$config = $this->generateAppConfig();
748
-		$config->setValueBool('feed', 'bool', true);
749
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false));
750
-	}
751
-
752
-	public function testSetValueBoolCache(): void {
753
-		$config = $this->generateAppConfig();
754
-		$config->setValueBool('feed', 'bool', true);
755
-		$status = $config->statusCache();
756
-		$this->assertSame('1', $status['fastCache']['feed']['bool']);
757
-	}
758
-
759
-	public function testSetValueBoolDatabase(): void {
760
-		$config = $this->generateAppConfig();
761
-		$config->setValueBool('feed', 'bool', true);
762
-		$config->clearCache();
763
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false));
764
-	}
765
-
766
-	public function testSetValueBoolIsUpdated(): void {
767
-		$config = $this->generateAppConfig();
768
-		$config->setValueBool('feed', 'bool', true);
769
-		$this->assertSame(true, $config->setValueBool('feed', 'bool', false));
770
-	}
771
-
772
-	public function testSetValueBoolIsNotUpdated(): void {
773
-		$config = $this->generateAppConfig();
774
-		$config->setValueBool('feed', 'bool', true);
775
-		$this->assertSame(false, $config->setValueBool('feed', 'bool', true));
776
-	}
777
-
778
-	public function testSetValueBoolIsUpdatedCache(): void {
779
-		$config = $this->generateAppConfig();
780
-		$config->setValueBool('feed', 'bool', true);
781
-		$config->setValueBool('feed', 'bool', false);
782
-		$status = $config->statusCache();
783
-		$this->assertSame('0', $status['fastCache']['feed']['bool']);
784
-	}
785
-
786
-	public function testSetValueBoolIsUpdatedDatabase(): void {
787
-		$config = $this->generateAppConfig();
788
-		$config->setValueBool('feed', 'bool', true);
789
-		$config->setValueBool('feed', 'bool', false);
790
-		$config->clearCache();
791
-		$this->assertSame(false, $config->getValueBool('feed', 'bool', true));
792
-	}
793
-
794
-
795
-	public function testSetValueArray(): void {
796
-		$config = $this->generateAppConfig();
797
-		$config->setValueArray('feed', 'array', ['test' => 1]);
798
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
799
-	}
800
-
801
-	public function testSetValueArrayCache(): void {
802
-		$config = $this->generateAppConfig();
803
-		$config->setValueArray('feed', 'array', ['test' => 1]);
804
-		$status = $config->statusCache();
805
-		$this->assertSame('{"test":1}', $status['fastCache']['feed']['array']);
806
-	}
807
-
808
-	public function testSetValueArrayDatabase(): void {
809
-		$config = $this->generateAppConfig();
810
-		$config->setValueArray('feed', 'array', ['test' => 1]);
811
-		$config->clearCache();
812
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
813
-	}
814
-
815
-	public function testSetValueArrayIsUpdated(): void {
816
-		$config = $this->generateAppConfig();
817
-		$config->setValueArray('feed', 'array', ['test' => 1]);
818
-		$this->assertSame(true, $config->setValueArray('feed', 'array', ['test' => 2]));
819
-	}
820
-
821
-	public function testSetValueArrayIsNotUpdated(): void {
822
-		$config = $this->generateAppConfig();
823
-		$config->setValueArray('feed', 'array', ['test' => 1]);
824
-		$this->assertSame(false, $config->setValueArray('feed', 'array', ['test' => 1]));
825
-	}
826
-
827
-	public function testSetValueArrayIsUpdatedCache(): void {
828
-		$config = $this->generateAppConfig();
829
-		$config->setValueArray('feed', 'array', ['test' => 1]);
830
-		$config->setValueArray('feed', 'array', ['test' => 2]);
831
-		$status = $config->statusCache();
832
-		$this->assertSame('{"test":2}', $status['fastCache']['feed']['array']);
833
-	}
834
-
835
-	public function testSetValueArrayIsUpdatedDatabase(): void {
836
-		$config = $this->generateAppConfig();
837
-		$config->setValueArray('feed', 'array', ['test' => 1]);
838
-		$config->setValueArray('feed', 'array', ['test' => 2]);
839
-		$config->clearCache();
840
-		$this->assertSame(['test' => 2], $config->getValueArray('feed', 'array', []));
841
-	}
842
-
843
-	public function testSetLazyValueString(): void {
844
-		$config = $this->generateAppConfig();
845
-		$config->setValueString('feed', 'string', 'value-1', true);
846
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
847
-	}
848
-
849
-	public function testSetLazyValueStringCache(): void {
850
-		$config = $this->generateAppConfig();
851
-		$config->setValueString('feed', 'string', 'value-1', true);
852
-		$status = $config->statusCache();
853
-		$this->assertSame('value-1', $status['lazyCache']['feed']['string']);
854
-	}
855
-
856
-	public function testSetLazyValueStringDatabase(): void {
857
-		$config = $this->generateAppConfig();
858
-		$config->setValueString('feed', 'string', 'value-1', true);
859
-		$config->clearCache();
860
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
861
-	}
862
-
863
-	public function testSetLazyValueStringAsNonLazy(): void {
864
-		$config = $this->generateAppConfig();
865
-		$config->setValueString('feed', 'string', 'value-1', true);
866
-		$config->setValueString('feed', 'string', 'value-1', false);
867
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
868
-	}
869
-
870
-	public function testSetNonLazyValueStringAsLazy(): void {
871
-		$config = $this->generateAppConfig();
872
-		$config->setValueString('feed', 'string', 'value-1', false);
873
-		$config->setValueString('feed', 'string', 'value-1', true);
874
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
875
-	}
876
-
877
-	public function testSetSensitiveValueString(): void {
878
-		$config = $this->generateAppConfig();
879
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
880
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
881
-	}
882
-
883
-	public function testSetSensitiveValueStringCache(): void {
884
-		$config = $this->generateAppConfig();
885
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
886
-		$status = $config->statusCache();
887
-		$this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['string']);
888
-	}
889
-
890
-	public function testSetSensitiveValueStringDatabase(): void {
891
-		$config = $this->generateAppConfig();
892
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
893
-		$config->clearCache();
894
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
895
-	}
896
-
897
-	public function testSetNonSensitiveValueStringAsSensitive(): void {
898
-		$config = $this->generateAppConfig();
899
-		$config->setValueString('feed', 'string', 'value-1', sensitive: false);
900
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
901
-		$this->assertSame(true, $config->isSensitive('feed', 'string'));
902
-
903
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-1');
904
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-2');
905
-	}
906
-
907
-	public function testSetSensitiveValueStringAsNonSensitiveStaysSensitive(): void {
908
-		$config = $this->generateAppConfig();
909
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
910
-		$config->setValueString('feed', 'string', 'value-2', sensitive: false);
911
-		$this->assertSame(true, $config->isSensitive('feed', 'string'));
912
-
913
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-1');
914
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-2');
915
-	}
916
-
917
-	public function testSetSensitiveValueStringAsNonSensitiveAreStillUpdated(): void {
918
-		$config = $this->generateAppConfig();
919
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
920
-		$config->setValueString('feed', 'string', 'value-2', sensitive: false);
921
-		$this->assertSame('value-2', $config->getValueString('feed', 'string', ''));
922
-
923
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-1');
924
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-2');
925
-	}
926
-
927
-	public function testSetLazyValueInt(): void {
928
-		$config = $this->generateAppConfig();
929
-		$config->setValueInt('feed', 'int', 42, true);
930
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
931
-	}
932
-
933
-	public function testSetLazyValueIntCache(): void {
934
-		$config = $this->generateAppConfig();
935
-		$config->setValueInt('feed', 'int', 42, true);
936
-		$status = $config->statusCache();
937
-		$this->assertSame('42', $status['lazyCache']['feed']['int']);
938
-	}
939
-
940
-	public function testSetLazyValueIntDatabase(): void {
941
-		$config = $this->generateAppConfig();
942
-		$config->setValueInt('feed', 'int', 42, true);
943
-		$config->clearCache();
944
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
945
-	}
946
-
947
-	public function testSetLazyValueIntAsNonLazy(): void {
948
-		$config = $this->generateAppConfig();
949
-		$config->setValueInt('feed', 'int', 42, true);
950
-		$config->setValueInt('feed', 'int', 42, false);
951
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
952
-	}
953
-
954
-	public function testSetNonLazyValueIntAsLazy(): void {
955
-		$config = $this->generateAppConfig();
956
-		$config->setValueInt('feed', 'int', 42, false);
957
-		$config->setValueInt('feed', 'int', 42, true);
958
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
959
-	}
960
-
961
-	public function testSetSensitiveValueInt(): void {
962
-		$config = $this->generateAppConfig();
963
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
964
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
965
-	}
966
-
967
-	public function testSetSensitiveValueIntCache(): void {
968
-		$config = $this->generateAppConfig();
969
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
970
-		$status = $config->statusCache();
971
-		$this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['int']);
972
-	}
973
-
974
-	public function testSetSensitiveValueIntDatabase(): void {
975
-		$config = $this->generateAppConfig();
976
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
977
-		$config->clearCache();
978
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
979
-	}
980
-
981
-	public function testSetNonSensitiveValueIntAsSensitive(): void {
982
-		$config = $this->generateAppConfig();
983
-		$config->setValueInt('feed', 'int', 42);
984
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
985
-		$this->assertSame(true, $config->isSensitive('feed', 'int'));
986
-	}
987
-
988
-	public function testSetSensitiveValueIntAsNonSensitiveStaysSensitive(): void {
989
-		$config = $this->generateAppConfig();
990
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
991
-		$config->setValueInt('feed', 'int', 17);
992
-		$this->assertSame(true, $config->isSensitive('feed', 'int'));
993
-	}
994
-
995
-	public function testSetSensitiveValueIntAsNonSensitiveAreStillUpdated(): void {
996
-		$config = $this->generateAppConfig();
997
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
998
-		$config->setValueInt('feed', 'int', 17);
999
-		$this->assertSame(17, $config->getValueInt('feed', 'int', 0));
1000
-	}
1001
-
1002
-	public function testSetLazyValueFloat(): void {
1003
-		$config = $this->generateAppConfig();
1004
-		$config->setValueFloat('feed', 'float', 3.14, true);
1005
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1006
-	}
1007
-
1008
-	public function testSetLazyValueFloatCache(): void {
1009
-		$config = $this->generateAppConfig();
1010
-		$config->setValueFloat('feed', 'float', 3.14, true);
1011
-		$status = $config->statusCache();
1012
-		$this->assertSame('3.14', $status['lazyCache']['feed']['float']);
1013
-	}
1014
-
1015
-	public function testSetLazyValueFloatDatabase(): void {
1016
-		$config = $this->generateAppConfig();
1017
-		$config->setValueFloat('feed', 'float', 3.14, true);
1018
-		$config->clearCache();
1019
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1020
-	}
1021
-
1022
-	public function testSetLazyValueFloatAsNonLazy(): void {
1023
-		$config = $this->generateAppConfig();
1024
-		$config->setValueFloat('feed', 'float', 3.14, true);
1025
-		$config->setValueFloat('feed', 'float', 3.14, false);
1026
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1027
-	}
1028
-
1029
-	public function testSetNonLazyValueFloatAsLazy(): void {
1030
-		$config = $this->generateAppConfig();
1031
-		$config->setValueFloat('feed', 'float', 3.14, false);
1032
-		$config->setValueFloat('feed', 'float', 3.14, true);
1033
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1034
-	}
1035
-
1036
-	public function testSetSensitiveValueFloat(): void {
1037
-		$config = $this->generateAppConfig();
1038
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1039
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1040
-	}
1041
-
1042
-	public function testSetSensitiveValueFloatCache(): void {
1043
-		$config = $this->generateAppConfig();
1044
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1045
-		$status = $config->statusCache();
1046
-		$this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['float']);
1047
-	}
1048
-
1049
-	public function testSetSensitiveValueFloatDatabase(): void {
1050
-		$config = $this->generateAppConfig();
1051
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1052
-		$config->clearCache();
1053
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1054
-	}
1055
-
1056
-	public function testSetNonSensitiveValueFloatAsSensitive(): void {
1057
-		$config = $this->generateAppConfig();
1058
-		$config->setValueFloat('feed', 'float', 3.14);
1059
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1060
-		$this->assertSame(true, $config->isSensitive('feed', 'float'));
1061
-	}
1062
-
1063
-	public function testSetSensitiveValueFloatAsNonSensitiveStaysSensitive(): void {
1064
-		$config = $this->generateAppConfig();
1065
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1066
-		$config->setValueFloat('feed', 'float', 1.23);
1067
-		$this->assertSame(true, $config->isSensitive('feed', 'float'));
1068
-	}
1069
-
1070
-	public function testSetSensitiveValueFloatAsNonSensitiveAreStillUpdated(): void {
1071
-		$config = $this->generateAppConfig();
1072
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1073
-		$config->setValueFloat('feed', 'float', 1.23);
1074
-		$this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0));
1075
-	}
1076
-
1077
-	public function testSetLazyValueBool(): void {
1078
-		$config = $this->generateAppConfig();
1079
-		$config->setValueBool('feed', 'bool', true, true);
1080
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1081
-	}
1082
-
1083
-	public function testSetLazyValueBoolCache(): void {
1084
-		$config = $this->generateAppConfig();
1085
-		$config->setValueBool('feed', 'bool', true, true);
1086
-		$status = $config->statusCache();
1087
-		$this->assertSame('1', $status['lazyCache']['feed']['bool']);
1088
-	}
1089
-
1090
-	public function testSetLazyValueBoolDatabase(): void {
1091
-		$config = $this->generateAppConfig();
1092
-		$config->setValueBool('feed', 'bool', true, true);
1093
-		$config->clearCache();
1094
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1095
-	}
1096
-
1097
-	public function testSetLazyValueBoolAsNonLazy(): void {
1098
-		$config = $this->generateAppConfig();
1099
-		$config->setValueBool('feed', 'bool', true, true);
1100
-		$config->setValueBool('feed', 'bool', true, false);
1101
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false));
1102
-	}
1103
-
1104
-	public function testSetNonLazyValueBoolAsLazy(): void {
1105
-		$config = $this->generateAppConfig();
1106
-		$config->setValueBool('feed', 'bool', true, false);
1107
-		$config->setValueBool('feed', 'bool', true, true);
1108
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1109
-	}
1110
-
1111
-	public function testSetLazyValueArray(): void {
1112
-		$config = $this->generateAppConfig();
1113
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1114
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1115
-	}
1116
-
1117
-	public function testSetLazyValueArrayCache(): void {
1118
-		$config = $this->generateAppConfig();
1119
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1120
-		$status = $config->statusCache();
1121
-		$this->assertSame('{"test":1}', $status['lazyCache']['feed']['array']);
1122
-	}
1123
-
1124
-	public function testSetLazyValueArrayDatabase(): void {
1125
-		$config = $this->generateAppConfig();
1126
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1127
-		$config->clearCache();
1128
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1129
-	}
1130
-
1131
-	public function testSetLazyValueArrayAsNonLazy(): void {
1132
-		$config = $this->generateAppConfig();
1133
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1134
-		$config->setValueArray('feed', 'array', ['test' => 1], false);
1135
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
1136
-	}
1137
-
1138
-	public function testSetNonLazyValueArrayAsLazy(): void {
1139
-		$config = $this->generateAppConfig();
1140
-		$config->setValueArray('feed', 'array', ['test' => 1], false);
1141
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1142
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1143
-	}
1144
-
1145
-
1146
-	public function testSetSensitiveValueArray(): void {
1147
-		$config = $this->generateAppConfig();
1148
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1149
-		$this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', []));
1150
-	}
1151
-
1152
-	public function testSetSensitiveValueArrayCache(): void {
1153
-		$config = $this->generateAppConfig();
1154
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1155
-		$status = $config->statusCache();
1156
-		$this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['array']);
1157
-	}
1158
-
1159
-	public function testSetSensitiveValueArrayDatabase(): void {
1160
-		$config = $this->generateAppConfig();
1161
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1162
-		$config->clearCache();
1163
-		$this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', []));
1164
-	}
1165
-
1166
-	public function testSetNonSensitiveValueArrayAsSensitive(): void {
1167
-		$config = $this->generateAppConfig();
1168
-		$config->setValueArray('feed', 'array', ['test' => 1]);
1169
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1170
-		$this->assertSame(true, $config->isSensitive('feed', 'array'));
1171
-	}
1172
-
1173
-	public function testSetSensitiveValueArrayAsNonSensitiveStaysSensitive(): void {
1174
-		$config = $this->generateAppConfig();
1175
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1176
-		$config->setValueArray('feed', 'array', ['test' => 2]);
1177
-		$this->assertSame(true, $config->isSensitive('feed', 'array'));
1178
-	}
1179
-
1180
-	public function testSetSensitiveValueArrayAsNonSensitiveAreStillUpdated(): void {
1181
-		$config = $this->generateAppConfig();
1182
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1183
-		$config->setValueArray('feed', 'array', ['test' => 2]);
1184
-		$this->assertEqualsCanonicalizing(['test' => 2], $config->getValueArray('feed', 'array', []));
1185
-	}
1186
-
1187
-	public function testUpdateNotSensitiveToSensitive(): void {
1188
-		$config = $this->generateAppConfig();
1189
-		$config->updateSensitive('non-sensitive-app', 'lazy-key', true);
1190
-		$this->assertSame(true, $config->isSensitive('non-sensitive-app', 'lazy-key', true));
1191
-	}
1192
-
1193
-	public function testUpdateSensitiveToNotSensitive(): void {
1194
-		$config = $this->generateAppConfig();
1195
-		$config->updateSensitive('sensitive-app', 'lazy-key', false);
1196
-		$this->assertSame(false, $config->isSensitive('sensitive-app', 'lazy-key', true));
1197
-	}
1198
-
1199
-	public function testUpdateSensitiveToSensitiveReturnsFalse(): void {
1200
-		$config = $this->generateAppConfig();
1201
-		$this->assertSame(false, $config->updateSensitive('sensitive-app', 'lazy-key', true));
1202
-	}
1203
-
1204
-	public function testUpdateNotSensitiveToNotSensitiveReturnsFalse(): void {
1205
-		$config = $this->generateAppConfig();
1206
-		$this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'lazy-key', false));
1207
-	}
1208
-
1209
-	public function testUpdateSensitiveOnUnknownKeyReturnsFalse(): void {
1210
-		$config = $this->generateAppConfig();
1211
-		$this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'unknown-key', true));
1212
-	}
1213
-
1214
-	public function testUpdateNotLazyToLazy(): void {
1215
-		$config = $this->generateAppConfig();
1216
-		$config->updateLazy('non-sensitive-app', 'non-lazy-key', true);
1217
-		$this->assertSame(true, $config->isLazy('non-sensitive-app', 'non-lazy-key'));
1218
-	}
1219
-
1220
-	public function testUpdateLazyToNotLazy(): void {
1221
-		$config = $this->generateAppConfig();
1222
-		$config->updateLazy('non-sensitive-app', 'lazy-key', false);
1223
-		$this->assertSame(false, $config->isLazy('non-sensitive-app', 'lazy-key'));
1224
-	}
1225
-
1226
-	public function testUpdateLazyToLazyReturnsFalse(): void {
1227
-		$config = $this->generateAppConfig();
1228
-		$this->assertSame(false, $config->updateLazy('non-sensitive-app', 'lazy-key', true));
1229
-	}
1230
-
1231
-	public function testUpdateNotLazyToNotLazyReturnsFalse(): void {
1232
-		$config = $this->generateAppConfig();
1233
-		$this->assertSame(false, $config->updateLazy('non-sensitive-app', 'non-lazy-key', false));
1234
-	}
1235
-
1236
-	public function testUpdateLazyOnUnknownKeyReturnsFalse(): void {
1237
-		$config = $this->generateAppConfig();
1238
-		$this->assertSame(false, $config->updateLazy('non-sensitive-app', 'unknown-key', true));
1239
-	}
1240
-
1241
-	public function testGetDetails(): void {
1242
-		$config = $this->generateAppConfig();
1243
-		$this->assertEquals(
1244
-			[
1245
-				'app' => 'non-sensitive-app',
1246
-				'key' => 'lazy-key',
1247
-				'value' => 'value',
1248
-				'type' => 4,
1249
-				'lazy' => true,
1250
-				'typeString' => 'string',
1251
-				'sensitive' => false,
1252
-			],
1253
-			$config->getDetails('non-sensitive-app', 'lazy-key')
1254
-		);
1255
-	}
1256
-
1257
-	public function testGetDetailsSensitive(): void {
1258
-		$config = $this->generateAppConfig();
1259
-		$this->assertEquals(
1260
-			[
1261
-				'app' => 'sensitive-app',
1262
-				'key' => 'lazy-key',
1263
-				'value' => 'value',
1264
-				'type' => 4,
1265
-				'lazy' => true,
1266
-				'typeString' => 'string',
1267
-				'sensitive' => true,
1268
-			],
1269
-			$config->getDetails('sensitive-app', 'lazy-key')
1270
-		);
1271
-	}
1272
-
1273
-	public function testGetDetailsInt(): void {
1274
-		$config = $this->generateAppConfig();
1275
-		$this->assertEquals(
1276
-			[
1277
-				'app' => 'typed',
1278
-				'key' => 'int',
1279
-				'value' => '42',
1280
-				'type' => 8,
1281
-				'lazy' => false,
1282
-				'typeString' => 'integer',
1283
-				'sensitive' => false
1284
-			],
1285
-			$config->getDetails('typed', 'int')
1286
-		);
1287
-	}
1288
-
1289
-	public function testGetDetailsFloat(): void {
1290
-		$config = $this->generateAppConfig();
1291
-		$this->assertEquals(
1292
-			[
1293
-				'app' => 'typed',
1294
-				'key' => 'float',
1295
-				'value' => '3.14',
1296
-				'type' => 16,
1297
-				'lazy' => false,
1298
-				'typeString' => 'float',
1299
-				'sensitive' => false
1300
-			],
1301
-			$config->getDetails('typed', 'float')
1302
-		);
1303
-	}
1304
-
1305
-	public function testGetDetailsBool(): void {
1306
-		$config = $this->generateAppConfig();
1307
-		$this->assertEquals(
1308
-			[
1309
-				'app' => 'typed',
1310
-				'key' => 'bool',
1311
-				'value' => '1',
1312
-				'type' => 32,
1313
-				'lazy' => false,
1314
-				'typeString' => 'boolean',
1315
-				'sensitive' => false
1316
-			],
1317
-			$config->getDetails('typed', 'bool')
1318
-		);
1319
-	}
1320
-
1321
-	public function testGetDetailsArray(): void {
1322
-		$config = $this->generateAppConfig();
1323
-		$this->assertEquals(
1324
-			[
1325
-				'app' => 'typed',
1326
-				'key' => 'array',
1327
-				'value' => '{"test": 1}',
1328
-				'type' => 64,
1329
-				'lazy' => false,
1330
-				'typeString' => 'array',
1331
-				'sensitive' => false
1332
-			],
1333
-			$config->getDetails('typed', 'array')
1334
-		);
1335
-	}
1336
-
1337
-	public function testDeleteKey(): void {
1338
-		$config = $this->generateAppConfig();
1339
-		$config->deleteKey('anotherapp', 'key');
1340
-		$this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1341
-	}
1342
-
1343
-	public function testDeleteKeyCache(): void {
1344
-		$config = $this->generateAppConfig();
1345
-		$config->deleteKey('anotherapp', 'key');
1346
-		$status = $config->statusCache();
1347
-		$this->assertEqualsCanonicalizing(['enabled' => 'no', 'installed_version' => '3.2.1'], $status['fastCache']['anotherapp']);
1348
-	}
1349
-
1350
-	public function testDeleteKeyDatabase(): void {
1351
-		$config = $this->generateAppConfig();
1352
-		$config->deleteKey('anotherapp', 'key');
1353
-		$config->clearCache();
1354
-		$this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1355
-	}
1356
-
1357
-	public function testDeleteApp(): void {
1358
-		$config = $this->generateAppConfig();
1359
-		$config->deleteApp('anotherapp');
1360
-		$this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1361
-		$this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default'));
1362
-	}
1363
-
1364
-	public function testDeleteAppCache(): void {
1365
-		$config = $this->generateAppConfig();
1366
-		$status = $config->statusCache();
1367
-		$this->assertSame(true, isset($status['fastCache']['anotherapp']));
1368
-		$config->deleteApp('anotherapp');
1369
-		$status = $config->statusCache();
1370
-		$this->assertSame(false, isset($status['fastCache']['anotherapp']));
1371
-	}
1372
-
1373
-	public function testDeleteAppDatabase(): void {
1374
-		$config = $this->generateAppConfig();
1375
-		$config->deleteApp('anotherapp');
1376
-		$config->clearCache();
1377
-		$this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1378
-		$this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default'));
1379
-	}
1380
-
1381
-	public function testClearCache(): void {
1382
-		$config = $this->generateAppConfig();
1383
-		$config->setValueString('feed', 'string', '123454');
1384
-		$config->clearCache();
1385
-		$status = $config->statusCache();
1386
-		$this->assertSame([], $status['fastCache']);
1387
-	}
1388
-
1389
-	public function testSensitiveValuesAreEncrypted(): void {
1390
-		$key = self::getUniqueID('secret');
1391
-
1392
-		$appConfig = $this->generateAppConfig();
1393
-		$secret = md5((string)time());
1394
-		$appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1395
-
1396
-		$this->assertConfigValueNotEquals('testapp', $key, $secret);
1397
-
1398
-		// Can get in same run
1399
-		$actualSecret = $appConfig->getValueString('testapp', $key);
1400
-		$this->assertEquals($secret, $actualSecret);
1401
-
1402
-		// Can get freshly decrypted from DB
1403
-		$newAppConfig = $this->generateAppConfig();
1404
-		$actualSecret = $newAppConfig->getValueString('testapp', $key);
1405
-		$this->assertEquals($secret, $actualSecret);
1406
-	}
1407
-
1408
-	public function testMigratingNonSensitiveValueToSensitiveWithSetValue(): void {
1409
-		$key = self::getUniqueID('secret');
1410
-		$appConfig = $this->generateAppConfig();
1411
-		$secret = sha1((string)time());
1412
-
1413
-		// Unencrypted
1414
-		$appConfig->setValueString('testapp', $key, $secret);
1415
-		$this->assertConfigKey('testapp', $key, $secret);
1416
-
1417
-		// Can get freshly decrypted from DB
1418
-		$newAppConfig = $this->generateAppConfig();
1419
-		$actualSecret = $newAppConfig->getValueString('testapp', $key);
1420
-		$this->assertEquals($secret, $actualSecret);
1421
-
1422
-		// Encrypting on change
1423
-		$appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1424
-		$this->assertConfigValueNotEquals('testapp', $key, $secret);
1425
-
1426
-		// Can get in same run
1427
-		$actualSecret = $appConfig->getValueString('testapp', $key);
1428
-		$this->assertEquals($secret, $actualSecret);
1429
-
1430
-		// Can get freshly decrypted from DB
1431
-		$newAppConfig = $this->generateAppConfig();
1432
-		$actualSecret = $newAppConfig->getValueString('testapp', $key);
1433
-		$this->assertEquals($secret, $actualSecret);
1434
-	}
1435
-
1436
-	public function testUpdateSensitiveValueToNonSensitiveWithUpdateSensitive(): void {
1437
-		$key = self::getUniqueID('secret');
1438
-		$appConfig = $this->generateAppConfig();
1439
-		$secret = sha1((string)time());
1440
-
1441
-		// Encrypted
1442
-		$appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1443
-		$this->assertConfigValueNotEquals('testapp', $key, $secret);
1444
-
1445
-		// Migrate to non-sensitive / non-encrypted
1446
-		$appConfig->updateSensitive('testapp', $key, false);
1447
-		$this->assertConfigKey('testapp', $key, $secret);
1448
-	}
1449
-
1450
-	public function testUpdateNonSensitiveValueToSensitiveWithUpdateSensitive(): void {
1451
-		$key = self::getUniqueID('secret');
1452
-		$appConfig = $this->generateAppConfig();
1453
-		$secret = sha1((string)time());
1454
-
1455
-		// Unencrypted
1456
-		$appConfig->setValueString('testapp', $key, $secret);
1457
-		$this->assertConfigKey('testapp', $key, $secret);
1458
-
1459
-		// Migrate to sensitive / encrypted
1460
-		$appConfig->updateSensitive('testapp', $key, true);
1461
-		$this->assertConfigValueNotEquals('testapp', $key, $secret);
1462
-	}
1463
-
1464
-	public function testSearchKeyNoLazyLoading(): void {
1465
-		$appConfig = $this->generateAppConfig();
1466
-		$appConfig->searchKeys('searchtest', 'search_');
1467
-		$status = $appConfig->statusCache();
1468
-		$this->assertFalse($status['lazyLoaded'], 'searchKeys() loaded lazy config');
1469
-	}
1470
-
1471
-	public function testSearchKeyFast(): void {
1472
-		$appConfig = $this->generateAppConfig();
1473
-		$this->assertEquals(['search_key1', 'search_key2', 'search_key3'], $appConfig->searchKeys('searchtest', 'search_'));
1474
-	}
1475
-
1476
-	public function testSearchKeyLazy(): void {
1477
-		$appConfig = $this->generateAppConfig();
1478
-		$this->assertEquals(['search_key5_lazy'], $appConfig->searchKeys('searchtest', 'search_', true));
1479
-	}
1480
-
1481
-	protected function loadConfigValueFromDatabase(string $app, string $key): string|false {
1482
-		$sql = $this->connection->getQueryBuilder();
1483
-		$sql->select('configvalue')
1484
-			->from('appconfig')
1485
-			->where($sql->expr()->eq('appid', $sql->createParameter('appid')))
1486
-			->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey')))
1487
-			->setParameter('appid', $app)
1488
-			->setParameter('configkey', $key);
1489
-		$query = $sql->executeQuery();
1490
-		$actual = $query->fetchOne();
1491
-		$query->closeCursor();
1492
-
1493
-		return $actual;
1494
-	}
1495
-
1496
-	protected function assertConfigKey(string $app, string $key, string|false $expected): void {
1497
-		$this->assertEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1498
-	}
1499
-
1500
-	protected function assertConfigValueNotEquals(string $app, string $key, string|false $expected): void {
1501
-		$this->assertNotEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1502
-	}
29
+    protected IAppConfig $appConfig;
30
+    protected IDBConnection $connection;
31
+    private IConfig $config;
32
+    private LoggerInterface $logger;
33
+    private ICrypto $crypto;
34
+
35
+    private array $originalConfig;
36
+
37
+    /**
38
+     * @var array<string, array<string, array<string, string, int, bool, bool>>>
39
+     *                                                                           [appId => [configKey, configValue, valueType, lazy, sensitive]]
40
+     */
41
+    private static array $baseStruct
42
+        = [
43
+            'testapp' => [
44
+                'enabled' => ['enabled', 'yes'],
45
+                'installed_version' => ['installed_version', '1.2.3'],
46
+                'depends_on' => ['depends_on', 'someapp'],
47
+                'deletethis' => ['deletethis', 'deletethis'],
48
+                'key' => ['key', 'value']
49
+            ],
50
+            'searchtest' => [
51
+                'search_key1' => ['search_key1', 'key1', IAppConfig::VALUE_STRING],
52
+                'search_key2' => ['search_key2', 'key2', IAppConfig::VALUE_STRING],
53
+                'search_key3' => ['search_key3', 'key3', IAppConfig::VALUE_STRING],
54
+                'searchnot_key4' => ['searchnot_key4', 'key4', IAppConfig::VALUE_STRING],
55
+                'search_key5_lazy' => ['search_key5_lazy', 'key5', IAppConfig::VALUE_STRING, true],
56
+            ],
57
+            'someapp' => [
58
+                'key' => ['key', 'value'],
59
+                'otherkey' => ['otherkey', 'othervalue']
60
+            ],
61
+            '123456' => [
62
+                'enabled' => ['enabled', 'yes'],
63
+                'key' => ['key', 'value']
64
+            ],
65
+            'anotherapp' => [
66
+                'enabled' => ['enabled', 'no'],
67
+                'installed_version' => ['installed_version', '3.2.1'],
68
+                'key' => ['key', 'value']
69
+            ],
70
+            'non-sensitive-app' => [
71
+                'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, false],
72
+                'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, false],
73
+            ],
74
+            'sensitive-app' => [
75
+                'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, true],
76
+                'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, true],
77
+            ],
78
+            'only-lazy' => [
79
+                'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true]
80
+            ],
81
+            'typed' => [
82
+                'mixed' => ['mixed', 'mix', IAppConfig::VALUE_MIXED],
83
+                'string' => ['string', 'value', IAppConfig::VALUE_STRING],
84
+                'int' => ['int', '42', IAppConfig::VALUE_INT],
85
+                'float' => ['float', '3.14', IAppConfig::VALUE_FLOAT],
86
+                'bool' => ['bool', '1', IAppConfig::VALUE_BOOL],
87
+                'array' => ['array', '{"test": 1}', IAppConfig::VALUE_ARRAY],
88
+            ],
89
+            'prefix-app' => [
90
+                'key1' => ['key1', 'value'],
91
+                'prefix1' => ['prefix1', 'value'],
92
+                'prefix-2' => ['prefix-2', 'value'],
93
+                'key-2' => ['key-2', 'value'],
94
+            ]
95
+        ];
96
+
97
+    protected function setUp(): void {
98
+        parent::setUp();
99
+
100
+        $this->connection = Server::get(IDBConnection::class);
101
+        $this->config = Server::get(IConfig::class);
102
+        $this->logger = Server::get(LoggerInterface::class);
103
+        $this->crypto = Server::get(ICrypto::class);
104
+
105
+        // storing current config and emptying the data table
106
+        $sql = $this->connection->getQueryBuilder();
107
+        $sql->select('*')
108
+            ->from('appconfig');
109
+        $result = $sql->executeQuery();
110
+        $this->originalConfig = $result->fetchAll();
111
+        $result->closeCursor();
112
+
113
+        $sql = $this->connection->getQueryBuilder();
114
+        $sql->delete('appconfig');
115
+        $sql->executeStatement();
116
+
117
+        $sql = $this->connection->getQueryBuilder();
118
+        $sql->insert('appconfig')
119
+            ->values(
120
+                [
121
+                    'appid' => $sql->createParameter('appid'),
122
+                    'configkey' => $sql->createParameter('configkey'),
123
+                    'configvalue' => $sql->createParameter('configvalue'),
124
+                    'type' => $sql->createParameter('type'),
125
+                    'lazy' => $sql->createParameter('lazy')
126
+                ]
127
+            );
128
+
129
+        foreach (self::$baseStruct as $appId => $appData) {
130
+            foreach ($appData as $key => $row) {
131
+                $value = $row[1];
132
+                $type = $row[2] ?? IAppConfig::VALUE_MIXED;
133
+                if (($row[4] ?? false) === true) {
134
+                    $type |= IAppConfig::VALUE_SENSITIVE;
135
+                    $value = self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX') . $this->crypto->encrypt($value);
136
+                    self::$baseStruct[$appId][$key]['encrypted'] = $value;
137
+                }
138
+
139
+                $sql->setParameters(
140
+                    [
141
+                        'appid' => $appId,
142
+                        'configkey' => $row[0],
143
+                        'configvalue' => $value,
144
+                        'type' => $type,
145
+                        'lazy' => (($row[3] ?? false) === true) ? 1 : 0
146
+                    ]
147
+                )->executeStatement();
148
+            }
149
+        }
150
+    }
151
+
152
+    protected function tearDown(): void {
153
+        $sql = $this->connection->getQueryBuilder();
154
+        $sql->delete('appconfig');
155
+        $sql->executeStatement();
156
+
157
+        $sql = $this->connection->getQueryBuilder();
158
+        $sql->insert('appconfig')
159
+            ->values(
160
+                [
161
+                    'appid' => $sql->createParameter('appid'),
162
+                    'configkey' => $sql->createParameter('configkey'),
163
+                    'configvalue' => $sql->createParameter('configvalue'),
164
+                    'lazy' => $sql->createParameter('lazy'),
165
+                    'type' => $sql->createParameter('type'),
166
+                ]
167
+            );
168
+
169
+        foreach ($this->originalConfig as $key => $configs) {
170
+            $sql->setParameter('appid', $configs['appid'])
171
+                ->setParameter('configkey', $configs['configkey'])
172
+                ->setParameter('configvalue', $configs['configvalue'])
173
+                ->setParameter('lazy', ($configs['lazy'] === '1') ? '1' : '0')
174
+                ->setParameter('type', $configs['type']);
175
+            $sql->executeStatement();
176
+        }
177
+
178
+        //		$this->restoreService(AppConfig::class);
179
+        parent::tearDown();
180
+    }
181
+
182
+    /**
183
+     * @param bool $preLoading TRUE will preload the 'fast' cache, which is the normal behavior of usual
184
+     *                         IAppConfig
185
+     *
186
+     * @return IAppConfig
187
+     */
188
+    private function generateAppConfig(bool $preLoading = true): IAppConfig {
189
+        /** @var AppConfig $config */
190
+        $config = new AppConfig(
191
+            $this->connection,
192
+            $this->config,
193
+            $this->logger,
194
+            $this->crypto,
195
+        );
196
+        $msg = ' generateAppConfig() failed to confirm cache status';
197
+
198
+        // confirm cache status
199
+        $status = $config->statusCache();
200
+        $this->assertSame(false, $status['fastLoaded'], $msg);
201
+        $this->assertSame(false, $status['lazyLoaded'], $msg);
202
+        $this->assertSame([], $status['fastCache'], $msg);
203
+        $this->assertSame([], $status['lazyCache'], $msg);
204
+        if ($preLoading) {
205
+            // simple way to initiate the load of non-lazy config values in cache
206
+            $config->getValueString('core', 'preload', '');
207
+
208
+            // confirm cache status
209
+            $status = $config->statusCache();
210
+            $this->assertSame(true, $status['fastLoaded'], $msg);
211
+            $this->assertSame(false, $status['lazyLoaded'], $msg);
212
+
213
+            $apps = array_values(array_diff(array_keys(self::$baseStruct), ['only-lazy']));
214
+            $this->assertEqualsCanonicalizing($apps, array_keys($status['fastCache']), $msg);
215
+            $this->assertSame([], array_keys($status['lazyCache']), $msg);
216
+        }
217
+
218
+        return $config;
219
+    }
220
+
221
+    public function testGetApps(): void {
222
+        $config = $this->generateAppConfig(false);
223
+
224
+        $this->assertEqualsCanonicalizing(array_keys(self::$baseStruct), $config->getApps());
225
+    }
226
+
227
+    public function testGetAppInstalledVersions(): void {
228
+        $config = $this->generateAppConfig(false);
229
+
230
+        $this->assertEquals(
231
+            ['testapp' => '1.2.3', 'anotherapp' => '3.2.1'],
232
+            $config->getAppInstalledVersions(false)
233
+        );
234
+        $this->assertEquals(
235
+            ['testapp' => '1.2.3'],
236
+            $config->getAppInstalledVersions(true)
237
+        );
238
+    }
239
+
240
+    /**
241
+     * returns list of app and their keys
242
+     *
243
+     * @return array<string, string[]> ['appId' => ['key1', 'key2', ]]
244
+     * @see testGetKeys
245
+     */
246
+    public static function providerGetAppKeys(): array {
247
+        $appKeys = [];
248
+        foreach (self::$baseStruct as $appId => $appData) {
249
+            $keys = [];
250
+            foreach ($appData as $row) {
251
+                $keys[] = $row[0];
252
+            }
253
+            $appKeys[] = [(string)$appId, $keys];
254
+        }
255
+
256
+        return $appKeys;
257
+    }
258
+
259
+    /**
260
+     * returns list of config keys
261
+     *
262
+     * @return array<string, string, string, int, bool, bool> [appId, key, value, type, lazy, sensitive]
263
+     * @see testIsSensitive
264
+     * @see testIsLazy
265
+     * @see testGetKeys
266
+     */
267
+    public static function providerGetKeys(): array {
268
+        $appKeys = [];
269
+        foreach (self::$baseStruct as $appId => $appData) {
270
+            foreach ($appData as $row) {
271
+                $appKeys[] = [
272
+                    (string)$appId, $row[0], $row[1], $row[2] ?? IAppConfig::VALUE_MIXED, $row[3] ?? false,
273
+                    $row[4] ?? false
274
+                ];
275
+            }
276
+        }
277
+
278
+        return $appKeys;
279
+    }
280
+
281
+    /**
282
+     *
283
+     * @param string $appId
284
+     * @param array $expectedKeys
285
+     */
286
+    #[\PHPUnit\Framework\Attributes\DataProvider('providerGetAppKeys')]
287
+    public function testGetKeys(string $appId, array $expectedKeys): void {
288
+        $config = $this->generateAppConfig();
289
+        $this->assertEqualsCanonicalizing($expectedKeys, $config->getKeys($appId));
290
+    }
291
+
292
+    public function testGetKeysOnUnknownAppShouldReturnsEmptyArray(): void {
293
+        $config = $this->generateAppConfig();
294
+        $this->assertEqualsCanonicalizing([], $config->getKeys('unknown-app'));
295
+    }
296
+
297
+    /**
298
+     *
299
+     * @param string $appId
300
+     * @param string $configKey
301
+     * @param string $value
302
+     * @param bool $lazy
303
+     */
304
+    #[\PHPUnit\Framework\Attributes\DataProvider('providerGetKeys')]
305
+    public function testHasKey(string $appId, string $configKey, string $value, int $type, bool $lazy): void {
306
+        $config = $this->generateAppConfig();
307
+        $this->assertEquals(true, $config->hasKey($appId, $configKey, $lazy));
308
+    }
309
+
310
+    public function testHasKeyOnNonExistentKeyReturnsFalse(): void {
311
+        $config = $this->generateAppConfig();
312
+        $this->assertEquals(false, $config->hasKey(array_keys(self::$baseStruct)[0], 'inexistant-key'));
313
+    }
314
+
315
+    public function testHasKeyOnUnknownAppReturnsFalse(): void {
316
+        $config = $this->generateAppConfig();
317
+        $this->assertEquals(false, $config->hasKey('inexistant-app', 'inexistant-key'));
318
+    }
319
+
320
+    public function testHasKeyOnMistypedAsLazyReturnsFalse(): void {
321
+        $config = $this->generateAppConfig();
322
+        $this->assertSame(false, $config->hasKey('non-sensitive-app', 'non-lazy-key', true));
323
+    }
324
+
325
+    public function testHasKeyOnMistypeAsNonLazyReturnsFalse(): void {
326
+        $config = $this->generateAppConfig();
327
+        $this->assertSame(false, $config->hasKey('non-sensitive-app', 'lazy-key', false));
328
+    }
329
+
330
+    public function testHasKeyOnMistypeAsNonLazyReturnsTrueWithLazyArgumentIsNull(): void {
331
+        $config = $this->generateAppConfig();
332
+        $this->assertSame(true, $config->hasKey('non-sensitive-app', 'lazy-key', null));
333
+    }
334
+
335
+    #[\PHPUnit\Framework\Attributes\DataProvider('providerGetKeys')]
336
+    public function testIsSensitive(
337
+        string $appId, string $configKey, string $configValue, int $type, bool $lazy, bool $sensitive,
338
+    ): void {
339
+        $config = $this->generateAppConfig();
340
+        $this->assertEquals($sensitive, $config->isSensitive($appId, $configKey, $lazy));
341
+    }
342
+
343
+    public function testIsSensitiveOnNonExistentKeyThrowsException(): void {
344
+        $config = $this->generateAppConfig();
345
+        $this->expectException(AppConfigUnknownKeyException::class);
346
+        $config->isSensitive(array_keys(self::$baseStruct)[0], 'inexistant-key');
347
+    }
348
+
349
+    public function testIsSensitiveOnUnknownAppThrowsException(): void {
350
+        $config = $this->generateAppConfig();
351
+        $this->expectException(AppConfigUnknownKeyException::class);
352
+        $config->isSensitive('unknown-app', 'inexistant-key');
353
+    }
354
+
355
+    public function testIsSensitiveOnSensitiveMistypedAsLazy(): void {
356
+        $config = $this->generateAppConfig();
357
+        $this->assertSame(true, $config->isSensitive('sensitive-app', 'non-lazy-key', true));
358
+    }
359
+
360
+    public function testIsSensitiveOnNonSensitiveMistypedAsLazy(): void {
361
+        $config = $this->generateAppConfig();
362
+        $this->assertSame(false, $config->isSensitive('non-sensitive-app', 'non-lazy-key', true));
363
+    }
364
+
365
+    public function testIsSensitiveOnSensitiveMistypedAsNonLazyThrowsException(): void {
366
+        $config = $this->generateAppConfig();
367
+        $this->expectException(AppConfigUnknownKeyException::class);
368
+        $config->isSensitive('sensitive-app', 'lazy-key', false);
369
+    }
370
+
371
+    public function testIsSensitiveOnNonSensitiveMistypedAsNonLazyThrowsException(): void {
372
+        $config = $this->generateAppConfig();
373
+        $this->expectException(AppConfigUnknownKeyException::class);
374
+        $config->isSensitive('non-sensitive-app', 'lazy-key', false);
375
+    }
376
+
377
+    #[\PHPUnit\Framework\Attributes\DataProvider('providerGetKeys')]
378
+    public function testIsLazy(string $appId, string $configKey, string $configValue, int $type, bool $lazy,
379
+    ): void {
380
+        $config = $this->generateAppConfig();
381
+        $this->assertEquals($lazy, $config->isLazy($appId, $configKey));
382
+    }
383
+
384
+    public function testIsLazyOnNonExistentKeyThrowsException(): void {
385
+        $config = $this->generateAppConfig();
386
+        $this->expectException(AppConfigUnknownKeyException::class);
387
+        $config->isLazy(array_keys(self::$baseStruct)[0], 'inexistant-key');
388
+    }
389
+
390
+    public function testIsLazyOnUnknownAppThrowsException(): void {
391
+        $config = $this->generateAppConfig();
392
+        $this->expectException(AppConfigUnknownKeyException::class);
393
+        $config->isLazy('unknown-app', 'inexistant-key');
394
+    }
395
+
396
+    public function testGetAllValues(): void {
397
+        $config = $this->generateAppConfig();
398
+        $this->assertEquals(
399
+            [
400
+                'array' => ['test' => 1],
401
+                'bool' => true,
402
+                'float' => 3.14,
403
+                'int' => 42,
404
+                'mixed' => 'mix',
405
+                'string' => 'value',
406
+            ],
407
+            $config->getAllValues('typed')
408
+        );
409
+    }
410
+
411
+    public function testGetAllValuesWithEmptyApp(): void {
412
+        $config = $this->generateAppConfig();
413
+        $this->expectException(InvalidArgumentException::class);
414
+        $config->getAllValues('');
415
+    }
416
+
417
+    /**
418
+     *
419
+     * @param string $appId
420
+     * @param array $keys
421
+     */
422
+    #[\PHPUnit\Framework\Attributes\DataProvider('providerGetAppKeys')]
423
+    public function testGetAllValuesWithEmptyKey(string $appId, array $keys): void {
424
+        $config = $this->generateAppConfig();
425
+        $this->assertEqualsCanonicalizing($keys, array_keys($config->getAllValues($appId, '')));
426
+    }
427
+
428
+    public function testGetAllValuesWithPrefix(): void {
429
+        $config = $this->generateAppConfig();
430
+        $this->assertEqualsCanonicalizing(['prefix1', 'prefix-2'], array_keys($config->getAllValues('prefix-app', 'prefix')));
431
+    }
432
+
433
+    public function testSearchValues(): void {
434
+        $config = $this->generateAppConfig();
435
+        $this->assertEqualsCanonicalizing(['testapp' => 'yes', '123456' => 'yes', 'anotherapp' => 'no'], $config->searchValues('enabled'));
436
+    }
437
+
438
+    public function testGetValueString(): void {
439
+        $config = $this->generateAppConfig();
440
+        $this->assertSame('value', $config->getValueString('typed', 'string', ''));
441
+    }
442
+
443
+    public function testGetValueStringOnUnknownAppReturnsDefault(): void {
444
+        $config = $this->generateAppConfig();
445
+        $this->assertSame('default-1', $config->getValueString('typed-1', 'string', 'default-1'));
446
+    }
447
+
448
+    public function testGetValueStringOnNonExistentKeyReturnsDefault(): void {
449
+        $config = $this->generateAppConfig();
450
+        $this->assertSame('default-2', $config->getValueString('typed', 'string-2', 'default-2'));
451
+    }
452
+
453
+    public function testGetValueStringOnWrongType(): void {
454
+        $config = $this->generateAppConfig();
455
+        $this->expectException(AppConfigTypeConflictException::class);
456
+        $config->getValueString('typed', 'int');
457
+    }
458
+
459
+    public function testGetNonLazyValueStringAsLazy(): void {
460
+        $config = $this->generateAppConfig();
461
+        $this->assertSame('value', $config->getValueString('non-sensitive-app', 'non-lazy-key', 'default', lazy: true));
462
+    }
463
+
464
+    public function testGetValueInt(): void {
465
+        $config = $this->generateAppConfig();
466
+        $this->assertSame(42, $config->getValueInt('typed', 'int', 0));
467
+    }
468
+
469
+    public function testGetValueIntOnUnknownAppReturnsDefault(): void {
470
+        $config = $this->generateAppConfig();
471
+        $this->assertSame(1, $config->getValueInt('typed-1', 'int', 1));
472
+    }
473
+
474
+    public function testGetValueIntOnNonExistentKeyReturnsDefault(): void {
475
+        $config = $this->generateAppConfig();
476
+        $this->assertSame(2, $config->getValueInt('typed', 'int-2', 2));
477
+    }
478
+
479
+    public function testGetValueIntOnWrongType(): void {
480
+        $config = $this->generateAppConfig();
481
+        $this->expectException(AppConfigTypeConflictException::class);
482
+        $config->getValueInt('typed', 'float');
483
+    }
484
+
485
+    public function testGetValueFloat(): void {
486
+        $config = $this->generateAppConfig();
487
+        $this->assertSame(3.14, $config->getValueFloat('typed', 'float', 0));
488
+    }
489
+
490
+    public function testGetValueFloatOnNonUnknownAppReturnsDefault(): void {
491
+        $config = $this->generateAppConfig();
492
+        $this->assertSame(1.11, $config->getValueFloat('typed-1', 'float', 1.11));
493
+    }
494
+
495
+    public function testGetValueFloatOnNonExistentKeyReturnsDefault(): void {
496
+        $config = $this->generateAppConfig();
497
+        $this->assertSame(2.22, $config->getValueFloat('typed', 'float-2', 2.22));
498
+    }
499
+
500
+    public function testGetValueFloatOnWrongType(): void {
501
+        $config = $this->generateAppConfig();
502
+        $this->expectException(AppConfigTypeConflictException::class);
503
+        $config->getValueFloat('typed', 'bool');
504
+    }
505
+
506
+    public function testGetValueBool(): void {
507
+        $config = $this->generateAppConfig();
508
+        $this->assertSame(true, $config->getValueBool('typed', 'bool'));
509
+    }
510
+
511
+    public function testGetValueBoolOnUnknownAppReturnsDefault(): void {
512
+        $config = $this->generateAppConfig();
513
+        $this->assertSame(false, $config->getValueBool('typed-1', 'bool', false));
514
+    }
515
+
516
+    public function testGetValueBoolOnNonExistentKeyReturnsDefault(): void {
517
+        $config = $this->generateAppConfig();
518
+        $this->assertSame(false, $config->getValueBool('typed', 'bool-2'));
519
+    }
520
+
521
+    public function testGetValueBoolOnWrongType(): void {
522
+        $config = $this->generateAppConfig();
523
+        $this->expectException(AppConfigTypeConflictException::class);
524
+        $config->getValueBool('typed', 'array');
525
+    }
526
+
527
+    public function testGetValueArray(): void {
528
+        $config = $this->generateAppConfig();
529
+        $this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('typed', 'array', []));
530
+    }
531
+
532
+    public function testGetValueArrayOnUnknownAppReturnsDefault(): void {
533
+        $config = $this->generateAppConfig();
534
+        $this->assertSame([1], $config->getValueArray('typed-1', 'array', [1]));
535
+    }
536
+
537
+    public function testGetValueArrayOnNonExistentKeyReturnsDefault(): void {
538
+        $config = $this->generateAppConfig();
539
+        $this->assertSame([1, 2], $config->getValueArray('typed', 'array-2', [1, 2]));
540
+    }
541
+
542
+    public function testGetValueArrayOnWrongType(): void {
543
+        $config = $this->generateAppConfig();
544
+        $this->expectException(AppConfigTypeConflictException::class);
545
+        $config->getValueArray('typed', 'string');
546
+    }
547
+
548
+
549
+    /**
550
+     * @return array
551
+     * @see testGetValueType
552
+     *
553
+     * @see testGetValueMixed
554
+     */
555
+    public static function providerGetValueMixed(): array {
556
+        return [
557
+            // key, value, type
558
+            ['mixed', 'mix', IAppConfig::VALUE_MIXED],
559
+            ['string', 'value', IAppConfig::VALUE_STRING],
560
+            ['int', '42', IAppConfig::VALUE_INT],
561
+            ['float', '3.14', IAppConfig::VALUE_FLOAT],
562
+            ['bool', '1', IAppConfig::VALUE_BOOL],
563
+            ['array', '{"test": 1}', IAppConfig::VALUE_ARRAY],
564
+        ];
565
+    }
566
+
567
+    /**
568
+     *
569
+     * @param string $key
570
+     * @param string $value
571
+     */
572
+    #[\PHPUnit\Framework\Attributes\DataProvider('providerGetValueMixed')]
573
+    public function testGetValueMixed(string $key, string $value): void {
574
+        $config = $this->generateAppConfig();
575
+        $this->assertSame($value, $config->getValueMixed('typed', $key));
576
+    }
577
+
578
+    /**
579
+     *
580
+     * @param string $key
581
+     * @param string $value
582
+     * @param int $type
583
+     */
584
+    #[\PHPUnit\Framework\Attributes\DataProvider('providerGetValueMixed')]
585
+    public function testGetValueType(string $key, string $value, int $type): void {
586
+        $config = $this->generateAppConfig();
587
+        $this->assertSame($type, $config->getValueType('typed', $key));
588
+    }
589
+
590
+    public function testGetValueTypeOnUnknownApp(): void {
591
+        $config = $this->generateAppConfig();
592
+        $this->expectException(AppConfigUnknownKeyException::class);
593
+        $config->getValueType('typed-1', 'string');
594
+    }
595
+
596
+    public function testGetValueTypeOnNonExistentKey(): void {
597
+        $config = $this->generateAppConfig();
598
+        $this->expectException(AppConfigUnknownKeyException::class);
599
+        $config->getValueType('typed', 'string-2');
600
+    }
601
+
602
+    public function testSetValueString(): void {
603
+        $config = $this->generateAppConfig();
604
+        $config->setValueString('feed', 'string', 'value-1');
605
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
606
+    }
607
+
608
+    public function testSetValueStringCache(): void {
609
+        $config = $this->generateAppConfig();
610
+        $config->setValueString('feed', 'string', 'value-1');
611
+        $status = $config->statusCache();
612
+        $this->assertSame('value-1', $status['fastCache']['feed']['string']);
613
+    }
614
+
615
+    public function testSetValueStringDatabase(): void {
616
+        $config = $this->generateAppConfig();
617
+        $config->setValueString('feed', 'string', 'value-1');
618
+        $config->clearCache();
619
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
620
+    }
621
+
622
+    public function testSetValueStringIsUpdated(): void {
623
+        $config = $this->generateAppConfig();
624
+        $config->setValueString('feed', 'string', 'value-1');
625
+        $this->assertSame(true, $config->setValueString('feed', 'string', 'value-2'));
626
+    }
627
+
628
+    public function testSetValueStringIsNotUpdated(): void {
629
+        $config = $this->generateAppConfig();
630
+        $config->setValueString('feed', 'string', 'value-1');
631
+        $this->assertSame(false, $config->setValueString('feed', 'string', 'value-1'));
632
+    }
633
+
634
+    public function testSetValueStringIsUpdatedCache(): void {
635
+        $config = $this->generateAppConfig();
636
+        $config->setValueString('feed', 'string', 'value-1');
637
+        $config->setValueString('feed', 'string', 'value-2');
638
+        $status = $config->statusCache();
639
+        $this->assertSame('value-2', $status['fastCache']['feed']['string']);
640
+    }
641
+
642
+    public function testSetValueStringIsUpdatedDatabase(): void {
643
+        $config = $this->generateAppConfig();
644
+        $config->setValueString('feed', 'string', 'value-1');
645
+        $config->setValueString('feed', 'string', 'value-2');
646
+        $config->clearCache();
647
+        $this->assertSame('value-2', $config->getValueString('feed', 'string', ''));
648
+    }
649
+
650
+    public function testSetValueInt(): void {
651
+        $config = $this->generateAppConfig();
652
+        $config->setValueInt('feed', 'int', 42);
653
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
654
+    }
655
+
656
+    public function testSetValueIntCache(): void {
657
+        $config = $this->generateAppConfig();
658
+        $config->setValueInt('feed', 'int', 42);
659
+        $status = $config->statusCache();
660
+        $this->assertSame('42', $status['fastCache']['feed']['int']);
661
+    }
662
+
663
+    public function testSetValueIntDatabase(): void {
664
+        $config = $this->generateAppConfig();
665
+        $config->setValueInt('feed', 'int', 42);
666
+        $config->clearCache();
667
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
668
+    }
669
+
670
+    public function testSetValueIntIsUpdated(): void {
671
+        $config = $this->generateAppConfig();
672
+        $config->setValueInt('feed', 'int', 42);
673
+        $this->assertSame(true, $config->setValueInt('feed', 'int', 17));
674
+    }
675
+
676
+    public function testSetValueIntIsNotUpdated(): void {
677
+        $config = $this->generateAppConfig();
678
+        $config->setValueInt('feed', 'int', 42);
679
+        $this->assertSame(false, $config->setValueInt('feed', 'int', 42));
680
+    }
681
+
682
+    public function testSetValueIntIsUpdatedCache(): void {
683
+        $config = $this->generateAppConfig();
684
+        $config->setValueInt('feed', 'int', 42);
685
+        $config->setValueInt('feed', 'int', 17);
686
+        $status = $config->statusCache();
687
+        $this->assertSame('17', $status['fastCache']['feed']['int']);
688
+    }
689
+
690
+    public function testSetValueIntIsUpdatedDatabase(): void {
691
+        $config = $this->generateAppConfig();
692
+        $config->setValueInt('feed', 'int', 42);
693
+        $config->setValueInt('feed', 'int', 17);
694
+        $config->clearCache();
695
+        $this->assertSame(17, $config->getValueInt('feed', 'int', 0));
696
+    }
697
+
698
+    public function testSetValueFloat(): void {
699
+        $config = $this->generateAppConfig();
700
+        $config->setValueFloat('feed', 'float', 3.14);
701
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
702
+    }
703
+
704
+    public function testSetValueFloatCache(): void {
705
+        $config = $this->generateAppConfig();
706
+        $config->setValueFloat('feed', 'float', 3.14);
707
+        $status = $config->statusCache();
708
+        $this->assertSame('3.14', $status['fastCache']['feed']['float']);
709
+    }
710
+
711
+    public function testSetValueFloatDatabase(): void {
712
+        $config = $this->generateAppConfig();
713
+        $config->setValueFloat('feed', 'float', 3.14);
714
+        $config->clearCache();
715
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
716
+    }
717
+
718
+    public function testSetValueFloatIsUpdated(): void {
719
+        $config = $this->generateAppConfig();
720
+        $config->setValueFloat('feed', 'float', 3.14);
721
+        $this->assertSame(true, $config->setValueFloat('feed', 'float', 1.23));
722
+    }
723
+
724
+    public function testSetValueFloatIsNotUpdated(): void {
725
+        $config = $this->generateAppConfig();
726
+        $config->setValueFloat('feed', 'float', 3.14);
727
+        $this->assertSame(false, $config->setValueFloat('feed', 'float', 3.14));
728
+    }
729
+
730
+    public function testSetValueFloatIsUpdatedCache(): void {
731
+        $config = $this->generateAppConfig();
732
+        $config->setValueFloat('feed', 'float', 3.14);
733
+        $config->setValueFloat('feed', 'float', 1.23);
734
+        $status = $config->statusCache();
735
+        $this->assertSame('1.23', $status['fastCache']['feed']['float']);
736
+    }
737
+
738
+    public function testSetValueFloatIsUpdatedDatabase(): void {
739
+        $config = $this->generateAppConfig();
740
+        $config->setValueFloat('feed', 'float', 3.14);
741
+        $config->setValueFloat('feed', 'float', 1.23);
742
+        $config->clearCache();
743
+        $this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0));
744
+    }
745
+
746
+    public function testSetValueBool(): void {
747
+        $config = $this->generateAppConfig();
748
+        $config->setValueBool('feed', 'bool', true);
749
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false));
750
+    }
751
+
752
+    public function testSetValueBoolCache(): void {
753
+        $config = $this->generateAppConfig();
754
+        $config->setValueBool('feed', 'bool', true);
755
+        $status = $config->statusCache();
756
+        $this->assertSame('1', $status['fastCache']['feed']['bool']);
757
+    }
758
+
759
+    public function testSetValueBoolDatabase(): void {
760
+        $config = $this->generateAppConfig();
761
+        $config->setValueBool('feed', 'bool', true);
762
+        $config->clearCache();
763
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false));
764
+    }
765
+
766
+    public function testSetValueBoolIsUpdated(): void {
767
+        $config = $this->generateAppConfig();
768
+        $config->setValueBool('feed', 'bool', true);
769
+        $this->assertSame(true, $config->setValueBool('feed', 'bool', false));
770
+    }
771
+
772
+    public function testSetValueBoolIsNotUpdated(): void {
773
+        $config = $this->generateAppConfig();
774
+        $config->setValueBool('feed', 'bool', true);
775
+        $this->assertSame(false, $config->setValueBool('feed', 'bool', true));
776
+    }
777
+
778
+    public function testSetValueBoolIsUpdatedCache(): void {
779
+        $config = $this->generateAppConfig();
780
+        $config->setValueBool('feed', 'bool', true);
781
+        $config->setValueBool('feed', 'bool', false);
782
+        $status = $config->statusCache();
783
+        $this->assertSame('0', $status['fastCache']['feed']['bool']);
784
+    }
785
+
786
+    public function testSetValueBoolIsUpdatedDatabase(): void {
787
+        $config = $this->generateAppConfig();
788
+        $config->setValueBool('feed', 'bool', true);
789
+        $config->setValueBool('feed', 'bool', false);
790
+        $config->clearCache();
791
+        $this->assertSame(false, $config->getValueBool('feed', 'bool', true));
792
+    }
793
+
794
+
795
+    public function testSetValueArray(): void {
796
+        $config = $this->generateAppConfig();
797
+        $config->setValueArray('feed', 'array', ['test' => 1]);
798
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
799
+    }
800
+
801
+    public function testSetValueArrayCache(): void {
802
+        $config = $this->generateAppConfig();
803
+        $config->setValueArray('feed', 'array', ['test' => 1]);
804
+        $status = $config->statusCache();
805
+        $this->assertSame('{"test":1}', $status['fastCache']['feed']['array']);
806
+    }
807
+
808
+    public function testSetValueArrayDatabase(): void {
809
+        $config = $this->generateAppConfig();
810
+        $config->setValueArray('feed', 'array', ['test' => 1]);
811
+        $config->clearCache();
812
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
813
+    }
814
+
815
+    public function testSetValueArrayIsUpdated(): void {
816
+        $config = $this->generateAppConfig();
817
+        $config->setValueArray('feed', 'array', ['test' => 1]);
818
+        $this->assertSame(true, $config->setValueArray('feed', 'array', ['test' => 2]));
819
+    }
820
+
821
+    public function testSetValueArrayIsNotUpdated(): void {
822
+        $config = $this->generateAppConfig();
823
+        $config->setValueArray('feed', 'array', ['test' => 1]);
824
+        $this->assertSame(false, $config->setValueArray('feed', 'array', ['test' => 1]));
825
+    }
826
+
827
+    public function testSetValueArrayIsUpdatedCache(): void {
828
+        $config = $this->generateAppConfig();
829
+        $config->setValueArray('feed', 'array', ['test' => 1]);
830
+        $config->setValueArray('feed', 'array', ['test' => 2]);
831
+        $status = $config->statusCache();
832
+        $this->assertSame('{"test":2}', $status['fastCache']['feed']['array']);
833
+    }
834
+
835
+    public function testSetValueArrayIsUpdatedDatabase(): void {
836
+        $config = $this->generateAppConfig();
837
+        $config->setValueArray('feed', 'array', ['test' => 1]);
838
+        $config->setValueArray('feed', 'array', ['test' => 2]);
839
+        $config->clearCache();
840
+        $this->assertSame(['test' => 2], $config->getValueArray('feed', 'array', []));
841
+    }
842
+
843
+    public function testSetLazyValueString(): void {
844
+        $config = $this->generateAppConfig();
845
+        $config->setValueString('feed', 'string', 'value-1', true);
846
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
847
+    }
848
+
849
+    public function testSetLazyValueStringCache(): void {
850
+        $config = $this->generateAppConfig();
851
+        $config->setValueString('feed', 'string', 'value-1', true);
852
+        $status = $config->statusCache();
853
+        $this->assertSame('value-1', $status['lazyCache']['feed']['string']);
854
+    }
855
+
856
+    public function testSetLazyValueStringDatabase(): void {
857
+        $config = $this->generateAppConfig();
858
+        $config->setValueString('feed', 'string', 'value-1', true);
859
+        $config->clearCache();
860
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
861
+    }
862
+
863
+    public function testSetLazyValueStringAsNonLazy(): void {
864
+        $config = $this->generateAppConfig();
865
+        $config->setValueString('feed', 'string', 'value-1', true);
866
+        $config->setValueString('feed', 'string', 'value-1', false);
867
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
868
+    }
869
+
870
+    public function testSetNonLazyValueStringAsLazy(): void {
871
+        $config = $this->generateAppConfig();
872
+        $config->setValueString('feed', 'string', 'value-1', false);
873
+        $config->setValueString('feed', 'string', 'value-1', true);
874
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
875
+    }
876
+
877
+    public function testSetSensitiveValueString(): void {
878
+        $config = $this->generateAppConfig();
879
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
880
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
881
+    }
882
+
883
+    public function testSetSensitiveValueStringCache(): void {
884
+        $config = $this->generateAppConfig();
885
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
886
+        $status = $config->statusCache();
887
+        $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['string']);
888
+    }
889
+
890
+    public function testSetSensitiveValueStringDatabase(): void {
891
+        $config = $this->generateAppConfig();
892
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
893
+        $config->clearCache();
894
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
895
+    }
896
+
897
+    public function testSetNonSensitiveValueStringAsSensitive(): void {
898
+        $config = $this->generateAppConfig();
899
+        $config->setValueString('feed', 'string', 'value-1', sensitive: false);
900
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
901
+        $this->assertSame(true, $config->isSensitive('feed', 'string'));
902
+
903
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-1');
904
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-2');
905
+    }
906
+
907
+    public function testSetSensitiveValueStringAsNonSensitiveStaysSensitive(): void {
908
+        $config = $this->generateAppConfig();
909
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
910
+        $config->setValueString('feed', 'string', 'value-2', sensitive: false);
911
+        $this->assertSame(true, $config->isSensitive('feed', 'string'));
912
+
913
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-1');
914
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-2');
915
+    }
916
+
917
+    public function testSetSensitiveValueStringAsNonSensitiveAreStillUpdated(): void {
918
+        $config = $this->generateAppConfig();
919
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
920
+        $config->setValueString('feed', 'string', 'value-2', sensitive: false);
921
+        $this->assertSame('value-2', $config->getValueString('feed', 'string', ''));
922
+
923
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-1');
924
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-2');
925
+    }
926
+
927
+    public function testSetLazyValueInt(): void {
928
+        $config = $this->generateAppConfig();
929
+        $config->setValueInt('feed', 'int', 42, true);
930
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
931
+    }
932
+
933
+    public function testSetLazyValueIntCache(): void {
934
+        $config = $this->generateAppConfig();
935
+        $config->setValueInt('feed', 'int', 42, true);
936
+        $status = $config->statusCache();
937
+        $this->assertSame('42', $status['lazyCache']['feed']['int']);
938
+    }
939
+
940
+    public function testSetLazyValueIntDatabase(): void {
941
+        $config = $this->generateAppConfig();
942
+        $config->setValueInt('feed', 'int', 42, true);
943
+        $config->clearCache();
944
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
945
+    }
946
+
947
+    public function testSetLazyValueIntAsNonLazy(): void {
948
+        $config = $this->generateAppConfig();
949
+        $config->setValueInt('feed', 'int', 42, true);
950
+        $config->setValueInt('feed', 'int', 42, false);
951
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
952
+    }
953
+
954
+    public function testSetNonLazyValueIntAsLazy(): void {
955
+        $config = $this->generateAppConfig();
956
+        $config->setValueInt('feed', 'int', 42, false);
957
+        $config->setValueInt('feed', 'int', 42, true);
958
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
959
+    }
960
+
961
+    public function testSetSensitiveValueInt(): void {
962
+        $config = $this->generateAppConfig();
963
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
964
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
965
+    }
966
+
967
+    public function testSetSensitiveValueIntCache(): void {
968
+        $config = $this->generateAppConfig();
969
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
970
+        $status = $config->statusCache();
971
+        $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['int']);
972
+    }
973
+
974
+    public function testSetSensitiveValueIntDatabase(): void {
975
+        $config = $this->generateAppConfig();
976
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
977
+        $config->clearCache();
978
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
979
+    }
980
+
981
+    public function testSetNonSensitiveValueIntAsSensitive(): void {
982
+        $config = $this->generateAppConfig();
983
+        $config->setValueInt('feed', 'int', 42);
984
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
985
+        $this->assertSame(true, $config->isSensitive('feed', 'int'));
986
+    }
987
+
988
+    public function testSetSensitiveValueIntAsNonSensitiveStaysSensitive(): void {
989
+        $config = $this->generateAppConfig();
990
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
991
+        $config->setValueInt('feed', 'int', 17);
992
+        $this->assertSame(true, $config->isSensitive('feed', 'int'));
993
+    }
994
+
995
+    public function testSetSensitiveValueIntAsNonSensitiveAreStillUpdated(): void {
996
+        $config = $this->generateAppConfig();
997
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
998
+        $config->setValueInt('feed', 'int', 17);
999
+        $this->assertSame(17, $config->getValueInt('feed', 'int', 0));
1000
+    }
1001
+
1002
+    public function testSetLazyValueFloat(): void {
1003
+        $config = $this->generateAppConfig();
1004
+        $config->setValueFloat('feed', 'float', 3.14, true);
1005
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1006
+    }
1007
+
1008
+    public function testSetLazyValueFloatCache(): void {
1009
+        $config = $this->generateAppConfig();
1010
+        $config->setValueFloat('feed', 'float', 3.14, true);
1011
+        $status = $config->statusCache();
1012
+        $this->assertSame('3.14', $status['lazyCache']['feed']['float']);
1013
+    }
1014
+
1015
+    public function testSetLazyValueFloatDatabase(): void {
1016
+        $config = $this->generateAppConfig();
1017
+        $config->setValueFloat('feed', 'float', 3.14, true);
1018
+        $config->clearCache();
1019
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1020
+    }
1021
+
1022
+    public function testSetLazyValueFloatAsNonLazy(): void {
1023
+        $config = $this->generateAppConfig();
1024
+        $config->setValueFloat('feed', 'float', 3.14, true);
1025
+        $config->setValueFloat('feed', 'float', 3.14, false);
1026
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1027
+    }
1028
+
1029
+    public function testSetNonLazyValueFloatAsLazy(): void {
1030
+        $config = $this->generateAppConfig();
1031
+        $config->setValueFloat('feed', 'float', 3.14, false);
1032
+        $config->setValueFloat('feed', 'float', 3.14, true);
1033
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1034
+    }
1035
+
1036
+    public function testSetSensitiveValueFloat(): void {
1037
+        $config = $this->generateAppConfig();
1038
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1039
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1040
+    }
1041
+
1042
+    public function testSetSensitiveValueFloatCache(): void {
1043
+        $config = $this->generateAppConfig();
1044
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1045
+        $status = $config->statusCache();
1046
+        $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['float']);
1047
+    }
1048
+
1049
+    public function testSetSensitiveValueFloatDatabase(): void {
1050
+        $config = $this->generateAppConfig();
1051
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1052
+        $config->clearCache();
1053
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1054
+    }
1055
+
1056
+    public function testSetNonSensitiveValueFloatAsSensitive(): void {
1057
+        $config = $this->generateAppConfig();
1058
+        $config->setValueFloat('feed', 'float', 3.14);
1059
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1060
+        $this->assertSame(true, $config->isSensitive('feed', 'float'));
1061
+    }
1062
+
1063
+    public function testSetSensitiveValueFloatAsNonSensitiveStaysSensitive(): void {
1064
+        $config = $this->generateAppConfig();
1065
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1066
+        $config->setValueFloat('feed', 'float', 1.23);
1067
+        $this->assertSame(true, $config->isSensitive('feed', 'float'));
1068
+    }
1069
+
1070
+    public function testSetSensitiveValueFloatAsNonSensitiveAreStillUpdated(): void {
1071
+        $config = $this->generateAppConfig();
1072
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1073
+        $config->setValueFloat('feed', 'float', 1.23);
1074
+        $this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0));
1075
+    }
1076
+
1077
+    public function testSetLazyValueBool(): void {
1078
+        $config = $this->generateAppConfig();
1079
+        $config->setValueBool('feed', 'bool', true, true);
1080
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1081
+    }
1082
+
1083
+    public function testSetLazyValueBoolCache(): void {
1084
+        $config = $this->generateAppConfig();
1085
+        $config->setValueBool('feed', 'bool', true, true);
1086
+        $status = $config->statusCache();
1087
+        $this->assertSame('1', $status['lazyCache']['feed']['bool']);
1088
+    }
1089
+
1090
+    public function testSetLazyValueBoolDatabase(): void {
1091
+        $config = $this->generateAppConfig();
1092
+        $config->setValueBool('feed', 'bool', true, true);
1093
+        $config->clearCache();
1094
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1095
+    }
1096
+
1097
+    public function testSetLazyValueBoolAsNonLazy(): void {
1098
+        $config = $this->generateAppConfig();
1099
+        $config->setValueBool('feed', 'bool', true, true);
1100
+        $config->setValueBool('feed', 'bool', true, false);
1101
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false));
1102
+    }
1103
+
1104
+    public function testSetNonLazyValueBoolAsLazy(): void {
1105
+        $config = $this->generateAppConfig();
1106
+        $config->setValueBool('feed', 'bool', true, false);
1107
+        $config->setValueBool('feed', 'bool', true, true);
1108
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1109
+    }
1110
+
1111
+    public function testSetLazyValueArray(): void {
1112
+        $config = $this->generateAppConfig();
1113
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1114
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1115
+    }
1116
+
1117
+    public function testSetLazyValueArrayCache(): void {
1118
+        $config = $this->generateAppConfig();
1119
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1120
+        $status = $config->statusCache();
1121
+        $this->assertSame('{"test":1}', $status['lazyCache']['feed']['array']);
1122
+    }
1123
+
1124
+    public function testSetLazyValueArrayDatabase(): void {
1125
+        $config = $this->generateAppConfig();
1126
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1127
+        $config->clearCache();
1128
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1129
+    }
1130
+
1131
+    public function testSetLazyValueArrayAsNonLazy(): void {
1132
+        $config = $this->generateAppConfig();
1133
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1134
+        $config->setValueArray('feed', 'array', ['test' => 1], false);
1135
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
1136
+    }
1137
+
1138
+    public function testSetNonLazyValueArrayAsLazy(): void {
1139
+        $config = $this->generateAppConfig();
1140
+        $config->setValueArray('feed', 'array', ['test' => 1], false);
1141
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1142
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1143
+    }
1144
+
1145
+
1146
+    public function testSetSensitiveValueArray(): void {
1147
+        $config = $this->generateAppConfig();
1148
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1149
+        $this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', []));
1150
+    }
1151
+
1152
+    public function testSetSensitiveValueArrayCache(): void {
1153
+        $config = $this->generateAppConfig();
1154
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1155
+        $status = $config->statusCache();
1156
+        $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['array']);
1157
+    }
1158
+
1159
+    public function testSetSensitiveValueArrayDatabase(): void {
1160
+        $config = $this->generateAppConfig();
1161
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1162
+        $config->clearCache();
1163
+        $this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', []));
1164
+    }
1165
+
1166
+    public function testSetNonSensitiveValueArrayAsSensitive(): void {
1167
+        $config = $this->generateAppConfig();
1168
+        $config->setValueArray('feed', 'array', ['test' => 1]);
1169
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1170
+        $this->assertSame(true, $config->isSensitive('feed', 'array'));
1171
+    }
1172
+
1173
+    public function testSetSensitiveValueArrayAsNonSensitiveStaysSensitive(): void {
1174
+        $config = $this->generateAppConfig();
1175
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1176
+        $config->setValueArray('feed', 'array', ['test' => 2]);
1177
+        $this->assertSame(true, $config->isSensitive('feed', 'array'));
1178
+    }
1179
+
1180
+    public function testSetSensitiveValueArrayAsNonSensitiveAreStillUpdated(): void {
1181
+        $config = $this->generateAppConfig();
1182
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1183
+        $config->setValueArray('feed', 'array', ['test' => 2]);
1184
+        $this->assertEqualsCanonicalizing(['test' => 2], $config->getValueArray('feed', 'array', []));
1185
+    }
1186
+
1187
+    public function testUpdateNotSensitiveToSensitive(): void {
1188
+        $config = $this->generateAppConfig();
1189
+        $config->updateSensitive('non-sensitive-app', 'lazy-key', true);
1190
+        $this->assertSame(true, $config->isSensitive('non-sensitive-app', 'lazy-key', true));
1191
+    }
1192
+
1193
+    public function testUpdateSensitiveToNotSensitive(): void {
1194
+        $config = $this->generateAppConfig();
1195
+        $config->updateSensitive('sensitive-app', 'lazy-key', false);
1196
+        $this->assertSame(false, $config->isSensitive('sensitive-app', 'lazy-key', true));
1197
+    }
1198
+
1199
+    public function testUpdateSensitiveToSensitiveReturnsFalse(): void {
1200
+        $config = $this->generateAppConfig();
1201
+        $this->assertSame(false, $config->updateSensitive('sensitive-app', 'lazy-key', true));
1202
+    }
1203
+
1204
+    public function testUpdateNotSensitiveToNotSensitiveReturnsFalse(): void {
1205
+        $config = $this->generateAppConfig();
1206
+        $this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'lazy-key', false));
1207
+    }
1208
+
1209
+    public function testUpdateSensitiveOnUnknownKeyReturnsFalse(): void {
1210
+        $config = $this->generateAppConfig();
1211
+        $this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'unknown-key', true));
1212
+    }
1213
+
1214
+    public function testUpdateNotLazyToLazy(): void {
1215
+        $config = $this->generateAppConfig();
1216
+        $config->updateLazy('non-sensitive-app', 'non-lazy-key', true);
1217
+        $this->assertSame(true, $config->isLazy('non-sensitive-app', 'non-lazy-key'));
1218
+    }
1219
+
1220
+    public function testUpdateLazyToNotLazy(): void {
1221
+        $config = $this->generateAppConfig();
1222
+        $config->updateLazy('non-sensitive-app', 'lazy-key', false);
1223
+        $this->assertSame(false, $config->isLazy('non-sensitive-app', 'lazy-key'));
1224
+    }
1225
+
1226
+    public function testUpdateLazyToLazyReturnsFalse(): void {
1227
+        $config = $this->generateAppConfig();
1228
+        $this->assertSame(false, $config->updateLazy('non-sensitive-app', 'lazy-key', true));
1229
+    }
1230
+
1231
+    public function testUpdateNotLazyToNotLazyReturnsFalse(): void {
1232
+        $config = $this->generateAppConfig();
1233
+        $this->assertSame(false, $config->updateLazy('non-sensitive-app', 'non-lazy-key', false));
1234
+    }
1235
+
1236
+    public function testUpdateLazyOnUnknownKeyReturnsFalse(): void {
1237
+        $config = $this->generateAppConfig();
1238
+        $this->assertSame(false, $config->updateLazy('non-sensitive-app', 'unknown-key', true));
1239
+    }
1240
+
1241
+    public function testGetDetails(): void {
1242
+        $config = $this->generateAppConfig();
1243
+        $this->assertEquals(
1244
+            [
1245
+                'app' => 'non-sensitive-app',
1246
+                'key' => 'lazy-key',
1247
+                'value' => 'value',
1248
+                'type' => 4,
1249
+                'lazy' => true,
1250
+                'typeString' => 'string',
1251
+                'sensitive' => false,
1252
+            ],
1253
+            $config->getDetails('non-sensitive-app', 'lazy-key')
1254
+        );
1255
+    }
1256
+
1257
+    public function testGetDetailsSensitive(): void {
1258
+        $config = $this->generateAppConfig();
1259
+        $this->assertEquals(
1260
+            [
1261
+                'app' => 'sensitive-app',
1262
+                'key' => 'lazy-key',
1263
+                'value' => 'value',
1264
+                'type' => 4,
1265
+                'lazy' => true,
1266
+                'typeString' => 'string',
1267
+                'sensitive' => true,
1268
+            ],
1269
+            $config->getDetails('sensitive-app', 'lazy-key')
1270
+        );
1271
+    }
1272
+
1273
+    public function testGetDetailsInt(): void {
1274
+        $config = $this->generateAppConfig();
1275
+        $this->assertEquals(
1276
+            [
1277
+                'app' => 'typed',
1278
+                'key' => 'int',
1279
+                'value' => '42',
1280
+                'type' => 8,
1281
+                'lazy' => false,
1282
+                'typeString' => 'integer',
1283
+                'sensitive' => false
1284
+            ],
1285
+            $config->getDetails('typed', 'int')
1286
+        );
1287
+    }
1288
+
1289
+    public function testGetDetailsFloat(): void {
1290
+        $config = $this->generateAppConfig();
1291
+        $this->assertEquals(
1292
+            [
1293
+                'app' => 'typed',
1294
+                'key' => 'float',
1295
+                'value' => '3.14',
1296
+                'type' => 16,
1297
+                'lazy' => false,
1298
+                'typeString' => 'float',
1299
+                'sensitive' => false
1300
+            ],
1301
+            $config->getDetails('typed', 'float')
1302
+        );
1303
+    }
1304
+
1305
+    public function testGetDetailsBool(): void {
1306
+        $config = $this->generateAppConfig();
1307
+        $this->assertEquals(
1308
+            [
1309
+                'app' => 'typed',
1310
+                'key' => 'bool',
1311
+                'value' => '1',
1312
+                'type' => 32,
1313
+                'lazy' => false,
1314
+                'typeString' => 'boolean',
1315
+                'sensitive' => false
1316
+            ],
1317
+            $config->getDetails('typed', 'bool')
1318
+        );
1319
+    }
1320
+
1321
+    public function testGetDetailsArray(): void {
1322
+        $config = $this->generateAppConfig();
1323
+        $this->assertEquals(
1324
+            [
1325
+                'app' => 'typed',
1326
+                'key' => 'array',
1327
+                'value' => '{"test": 1}',
1328
+                'type' => 64,
1329
+                'lazy' => false,
1330
+                'typeString' => 'array',
1331
+                'sensitive' => false
1332
+            ],
1333
+            $config->getDetails('typed', 'array')
1334
+        );
1335
+    }
1336
+
1337
+    public function testDeleteKey(): void {
1338
+        $config = $this->generateAppConfig();
1339
+        $config->deleteKey('anotherapp', 'key');
1340
+        $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1341
+    }
1342
+
1343
+    public function testDeleteKeyCache(): void {
1344
+        $config = $this->generateAppConfig();
1345
+        $config->deleteKey('anotherapp', 'key');
1346
+        $status = $config->statusCache();
1347
+        $this->assertEqualsCanonicalizing(['enabled' => 'no', 'installed_version' => '3.2.1'], $status['fastCache']['anotherapp']);
1348
+    }
1349
+
1350
+    public function testDeleteKeyDatabase(): void {
1351
+        $config = $this->generateAppConfig();
1352
+        $config->deleteKey('anotherapp', 'key');
1353
+        $config->clearCache();
1354
+        $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1355
+    }
1356
+
1357
+    public function testDeleteApp(): void {
1358
+        $config = $this->generateAppConfig();
1359
+        $config->deleteApp('anotherapp');
1360
+        $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1361
+        $this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default'));
1362
+    }
1363
+
1364
+    public function testDeleteAppCache(): void {
1365
+        $config = $this->generateAppConfig();
1366
+        $status = $config->statusCache();
1367
+        $this->assertSame(true, isset($status['fastCache']['anotherapp']));
1368
+        $config->deleteApp('anotherapp');
1369
+        $status = $config->statusCache();
1370
+        $this->assertSame(false, isset($status['fastCache']['anotherapp']));
1371
+    }
1372
+
1373
+    public function testDeleteAppDatabase(): void {
1374
+        $config = $this->generateAppConfig();
1375
+        $config->deleteApp('anotherapp');
1376
+        $config->clearCache();
1377
+        $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1378
+        $this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default'));
1379
+    }
1380
+
1381
+    public function testClearCache(): void {
1382
+        $config = $this->generateAppConfig();
1383
+        $config->setValueString('feed', 'string', '123454');
1384
+        $config->clearCache();
1385
+        $status = $config->statusCache();
1386
+        $this->assertSame([], $status['fastCache']);
1387
+    }
1388
+
1389
+    public function testSensitiveValuesAreEncrypted(): void {
1390
+        $key = self::getUniqueID('secret');
1391
+
1392
+        $appConfig = $this->generateAppConfig();
1393
+        $secret = md5((string)time());
1394
+        $appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1395
+
1396
+        $this->assertConfigValueNotEquals('testapp', $key, $secret);
1397
+
1398
+        // Can get in same run
1399
+        $actualSecret = $appConfig->getValueString('testapp', $key);
1400
+        $this->assertEquals($secret, $actualSecret);
1401
+
1402
+        // Can get freshly decrypted from DB
1403
+        $newAppConfig = $this->generateAppConfig();
1404
+        $actualSecret = $newAppConfig->getValueString('testapp', $key);
1405
+        $this->assertEquals($secret, $actualSecret);
1406
+    }
1407
+
1408
+    public function testMigratingNonSensitiveValueToSensitiveWithSetValue(): void {
1409
+        $key = self::getUniqueID('secret');
1410
+        $appConfig = $this->generateAppConfig();
1411
+        $secret = sha1((string)time());
1412
+
1413
+        // Unencrypted
1414
+        $appConfig->setValueString('testapp', $key, $secret);
1415
+        $this->assertConfigKey('testapp', $key, $secret);
1416
+
1417
+        // Can get freshly decrypted from DB
1418
+        $newAppConfig = $this->generateAppConfig();
1419
+        $actualSecret = $newAppConfig->getValueString('testapp', $key);
1420
+        $this->assertEquals($secret, $actualSecret);
1421
+
1422
+        // Encrypting on change
1423
+        $appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1424
+        $this->assertConfigValueNotEquals('testapp', $key, $secret);
1425
+
1426
+        // Can get in same run
1427
+        $actualSecret = $appConfig->getValueString('testapp', $key);
1428
+        $this->assertEquals($secret, $actualSecret);
1429
+
1430
+        // Can get freshly decrypted from DB
1431
+        $newAppConfig = $this->generateAppConfig();
1432
+        $actualSecret = $newAppConfig->getValueString('testapp', $key);
1433
+        $this->assertEquals($secret, $actualSecret);
1434
+    }
1435
+
1436
+    public function testUpdateSensitiveValueToNonSensitiveWithUpdateSensitive(): void {
1437
+        $key = self::getUniqueID('secret');
1438
+        $appConfig = $this->generateAppConfig();
1439
+        $secret = sha1((string)time());
1440
+
1441
+        // Encrypted
1442
+        $appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1443
+        $this->assertConfigValueNotEquals('testapp', $key, $secret);
1444
+
1445
+        // Migrate to non-sensitive / non-encrypted
1446
+        $appConfig->updateSensitive('testapp', $key, false);
1447
+        $this->assertConfigKey('testapp', $key, $secret);
1448
+    }
1449
+
1450
+    public function testUpdateNonSensitiveValueToSensitiveWithUpdateSensitive(): void {
1451
+        $key = self::getUniqueID('secret');
1452
+        $appConfig = $this->generateAppConfig();
1453
+        $secret = sha1((string)time());
1454
+
1455
+        // Unencrypted
1456
+        $appConfig->setValueString('testapp', $key, $secret);
1457
+        $this->assertConfigKey('testapp', $key, $secret);
1458
+
1459
+        // Migrate to sensitive / encrypted
1460
+        $appConfig->updateSensitive('testapp', $key, true);
1461
+        $this->assertConfigValueNotEquals('testapp', $key, $secret);
1462
+    }
1463
+
1464
+    public function testSearchKeyNoLazyLoading(): void {
1465
+        $appConfig = $this->generateAppConfig();
1466
+        $appConfig->searchKeys('searchtest', 'search_');
1467
+        $status = $appConfig->statusCache();
1468
+        $this->assertFalse($status['lazyLoaded'], 'searchKeys() loaded lazy config');
1469
+    }
1470
+
1471
+    public function testSearchKeyFast(): void {
1472
+        $appConfig = $this->generateAppConfig();
1473
+        $this->assertEquals(['search_key1', 'search_key2', 'search_key3'], $appConfig->searchKeys('searchtest', 'search_'));
1474
+    }
1475
+
1476
+    public function testSearchKeyLazy(): void {
1477
+        $appConfig = $this->generateAppConfig();
1478
+        $this->assertEquals(['search_key5_lazy'], $appConfig->searchKeys('searchtest', 'search_', true));
1479
+    }
1480
+
1481
+    protected function loadConfigValueFromDatabase(string $app, string $key): string|false {
1482
+        $sql = $this->connection->getQueryBuilder();
1483
+        $sql->select('configvalue')
1484
+            ->from('appconfig')
1485
+            ->where($sql->expr()->eq('appid', $sql->createParameter('appid')))
1486
+            ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey')))
1487
+            ->setParameter('appid', $app)
1488
+            ->setParameter('configkey', $key);
1489
+        $query = $sql->executeQuery();
1490
+        $actual = $query->fetchOne();
1491
+        $query->closeCursor();
1492
+
1493
+        return $actual;
1494
+    }
1495
+
1496
+    protected function assertConfigKey(string $app, string $key, string|false $expected): void {
1497
+        $this->assertEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1498
+    }
1499
+
1500
+    protected function assertConfigValueNotEquals(string $app, string $key, string|false $expected): void {
1501
+        $this->assertNotEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1502
+    }
1503 1503
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -132,7 +132,7 @@  discard block
 block discarded – undo
132 132
 				$type = $row[2] ?? IAppConfig::VALUE_MIXED;
133 133
 				if (($row[4] ?? false) === true) {
134 134
 					$type |= IAppConfig::VALUE_SENSITIVE;
135
-					$value = self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX') . $this->crypto->encrypt($value);
135
+					$value = self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX').$this->crypto->encrypt($value);
136 136
 					self::$baseStruct[$appId][$key]['encrypted'] = $value;
137 137
 				}
138 138
 
@@ -250,7 +250,7 @@  discard block
 block discarded – undo
250 250
 			foreach ($appData as $row) {
251 251
 				$keys[] = $row[0];
252 252
 			}
253
-			$appKeys[] = [(string)$appId, $keys];
253
+			$appKeys[] = [(string) $appId, $keys];
254 254
 		}
255 255
 
256 256
 		return $appKeys;
@@ -269,7 +269,7 @@  discard block
 block discarded – undo
269 269
 		foreach (self::$baseStruct as $appId => $appData) {
270 270
 			foreach ($appData as $row) {
271 271
 				$appKeys[] = [
272
-					(string)$appId, $row[0], $row[1], $row[2] ?? IAppConfig::VALUE_MIXED, $row[3] ?? false,
272
+					(string) $appId, $row[0], $row[1], $row[2] ?? IAppConfig::VALUE_MIXED, $row[3] ?? false,
273 273
 					$row[4] ?? false
274 274
 				];
275 275
 			}
@@ -1390,7 +1390,7 @@  discard block
 block discarded – undo
1390 1390
 		$key = self::getUniqueID('secret');
1391 1391
 
1392 1392
 		$appConfig = $this->generateAppConfig();
1393
-		$secret = md5((string)time());
1393
+		$secret = md5((string) time());
1394 1394
 		$appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1395 1395
 
1396 1396
 		$this->assertConfigValueNotEquals('testapp', $key, $secret);
@@ -1408,7 +1408,7 @@  discard block
 block discarded – undo
1408 1408
 	public function testMigratingNonSensitiveValueToSensitiveWithSetValue(): void {
1409 1409
 		$key = self::getUniqueID('secret');
1410 1410
 		$appConfig = $this->generateAppConfig();
1411
-		$secret = sha1((string)time());
1411
+		$secret = sha1((string) time());
1412 1412
 
1413 1413
 		// Unencrypted
1414 1414
 		$appConfig->setValueString('testapp', $key, $secret);
@@ -1436,7 +1436,7 @@  discard block
 block discarded – undo
1436 1436
 	public function testUpdateSensitiveValueToNonSensitiveWithUpdateSensitive(): void {
1437 1437
 		$key = self::getUniqueID('secret');
1438 1438
 		$appConfig = $this->generateAppConfig();
1439
-		$secret = sha1((string)time());
1439
+		$secret = sha1((string) time());
1440 1440
 
1441 1441
 		// Encrypted
1442 1442
 		$appConfig->setValueString('testapp', $key, $secret, sensitive: true);
@@ -1450,7 +1450,7 @@  discard block
 block discarded – undo
1450 1450
 	public function testUpdateNonSensitiveValueToSensitiveWithUpdateSensitive(): void {
1451 1451
 		$key = self::getUniqueID('secret');
1452 1452
 		$appConfig = $this->generateAppConfig();
1453
-		$secret = sha1((string)time());
1453
+		$secret = sha1((string) time());
1454 1454
 
1455 1455
 		// Unencrypted
1456 1456
 		$appConfig->setValueString('testapp', $key, $secret);
@@ -1478,7 +1478,7 @@  discard block
 block discarded – undo
1478 1478
 		$this->assertEquals(['search_key5_lazy'], $appConfig->searchKeys('searchtest', 'search_', true));
1479 1479
 	}
1480 1480
 
1481
-	protected function loadConfigValueFromDatabase(string $app, string $key): string|false {
1481
+	protected function loadConfigValueFromDatabase(string $app, string $key): string | false {
1482 1482
 		$sql = $this->connection->getQueryBuilder();
1483 1483
 		$sql->select('configvalue')
1484 1484
 			->from('appconfig')
@@ -1493,11 +1493,11 @@  discard block
 block discarded – undo
1493 1493
 		return $actual;
1494 1494
 	}
1495 1495
 
1496
-	protected function assertConfigKey(string $app, string $key, string|false $expected): void {
1496
+	protected function assertConfigKey(string $app, string $key, string | false $expected): void {
1497 1497
 		$this->assertEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1498 1498
 	}
1499 1499
 
1500
-	protected function assertConfigValueNotEquals(string $app, string $key, string|false $expected): void {
1500
+	protected function assertConfigValueNotEquals(string $app, string $key, string | false $expected): void {
1501 1501
 		$this->assertNotEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1502 1502
 	}
1503 1503
 }
Please login to merge, or discard this patch.
lib/private/AppConfig.php 1 patch
Indentation   +1735 added lines, -1735 removed lines patch added patch discarded remove patch
@@ -49,1739 +49,1739 @@
 block discarded – undo
49 49
  * @since 29.0.0 - Supporting types and lazy loading
50 50
  */
51 51
 class AppConfig implements IAppConfig {
52
-	private const APP_MAX_LENGTH = 32;
53
-	private const KEY_MAX_LENGTH = 64;
54
-	private const ENCRYPTION_PREFIX = '$AppConfigEncryption$';
55
-	private const ENCRYPTION_PREFIX_LENGTH = 21; // strlen(self::ENCRYPTION_PREFIX)
56
-
57
-	/** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
58
-	private array $fastCache = [];   // cache for normal config keys
59
-	/** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
60
-	private array $lazyCache = [];   // cache for lazy config keys
61
-	/** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
62
-	private array $valueTypes = [];  // type for all config values
63
-	private bool $fastLoaded = false;
64
-	private bool $lazyLoaded = false;
65
-	/** @var array<string, array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
66
-	private array $configLexiconDetails = [];
67
-	private bool $ignoreLexiconAliases = false;
68
-	private ?Preset $configLexiconPreset = null;
69
-	/** @var ?array<string, string> */
70
-	private ?array $appVersionsCache = null;
71
-
72
-	public function __construct(
73
-		protected IDBConnection $connection,
74
-		protected IConfig $config,
75
-		protected LoggerInterface $logger,
76
-		protected ICrypto $crypto,
77
-	) {
78
-	}
79
-
80
-	/**
81
-	 * @inheritDoc
82
-	 *
83
-	 * @return list<string> list of app ids
84
-	 * @since 7.0.0
85
-	 */
86
-	public function getApps(): array {
87
-		$this->loadConfigAll();
88
-		$apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
89
-		sort($apps);
90
-
91
-		return array_values(array_unique($apps));
92
-	}
93
-
94
-	/**
95
-	 * @inheritDoc
96
-	 *
97
-	 * @param string $app id of the app
98
-	 * @return list<string> list of stored config keys
99
-	 * @see searchKeys to not load lazy config keys
100
-	 *
101
-	 * @since 29.0.0
102
-	 */
103
-	public function getKeys(string $app): array {
104
-		$this->assertParams($app);
105
-		$this->loadConfigAll($app);
106
-		$keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
107
-		sort($keys);
108
-
109
-		return array_values(array_unique($keys));
110
-	}
111
-
112
-	/**
113
-	 * @inheritDoc
114
-	 *
115
-	 * @param string $app id of the app
116
-	 * @param string $prefix returns only keys starting with this value
117
-	 * @param bool $lazy TRUE to search in lazy config keys
118
-	 * @return list<string> list of stored config keys
119
-	 * @since 32.0.0
120
-	 */
121
-	public function searchKeys(string $app, string $prefix = '', bool $lazy = false): array {
122
-		$this->assertParams($app);
123
-		$this->loadConfig($app, $lazy);
124
-		if ($lazy) {
125
-			$keys = array_keys($this->lazyCache[$app] ?? []);
126
-		} else {
127
-			$keys = array_keys($this->fastCache[$app] ?? []);
128
-		}
129
-
130
-		if ($prefix !== '') {
131
-			$keys = array_filter($keys, static fn (string $key): bool => str_starts_with($key, $prefix));
132
-		}
133
-
134
-		sort($keys);
135
-		return array_values(array_unique($keys));
136
-	}
137
-
138
-	/**
139
-	 * @inheritDoc
140
-	 *
141
-	 * @param string $app id of the app
142
-	 * @param string $key config key
143
-	 * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
144
-	 *
145
-	 * @return bool TRUE if key exists
146
-	 * @since 7.0.0
147
-	 * @since 29.0.0 Added the $lazy argument
148
-	 */
149
-	public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
150
-		$this->assertParams($app, $key);
151
-		$this->loadConfig($app, $lazy);
152
-		$this->matchAndApplyLexiconDefinition($app, $key);
153
-
154
-		if ($lazy === null) {
155
-			$appCache = $this->getAllValues($app);
156
-			return isset($appCache[$key]);
157
-		}
158
-
159
-		if ($lazy) {
160
-			return isset($this->lazyCache[$app][$key]);
161
-		}
162
-
163
-		return isset($this->fastCache[$app][$key]);
164
-	}
165
-
166
-	/**
167
-	 * @param string $app id of the app
168
-	 * @param string $key config key
169
-	 * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
170
-	 *
171
-	 * @return bool
172
-	 * @throws AppConfigUnknownKeyException if config key is not known
173
-	 * @since 29.0.0
174
-	 */
175
-	public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
176
-		$this->assertParams($app, $key);
177
-		$this->loadConfig(null, $lazy);
178
-		$this->matchAndApplyLexiconDefinition($app, $key);
179
-
180
-		if (!isset($this->valueTypes[$app][$key])) {
181
-			throw new AppConfigUnknownKeyException('unknown config key');
182
-		}
183
-
184
-		return $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key]);
185
-	}
186
-
187
-	/**
188
-	 * @inheritDoc
189
-	 *
190
-	 * @param string $app if of the app
191
-	 * @param string $key config key
192
-	 *
193
-	 * @return bool TRUE if config is lazy loaded
194
-	 * @throws AppConfigUnknownKeyException if config key is not known
195
-	 * @see IAppConfig for details about lazy loading
196
-	 * @since 29.0.0
197
-	 */
198
-	public function isLazy(string $app, string $key): bool {
199
-		$this->assertParams($app, $key);
200
-		$this->matchAndApplyLexiconDefinition($app, $key);
201
-
202
-		// there is a huge probability the non-lazy config are already loaded
203
-		if ($this->hasKey($app, $key, false)) {
204
-			return false;
205
-		}
206
-
207
-		// key not found, we search in the lazy config
208
-		if ($this->hasKey($app, $key, true)) {
209
-			return true;
210
-		}
211
-
212
-		throw new AppConfigUnknownKeyException('unknown config key');
213
-	}
214
-
215
-
216
-	/**
217
-	 * @inheritDoc
218
-	 *
219
-	 * @param string $app id of the app
220
-	 * @param string $prefix config keys prefix to search
221
-	 * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
222
-	 *
223
-	 * @return array<string, string|int|float|bool|array> [configKey => configValue]
224
-	 * @since 29.0.0
225
-	 */
226
-	public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
227
-		$this->assertParams($app, $prefix);
228
-		// if we want to filter values, we need to get sensitivity
229
-		$this->loadConfigAll($app);
230
-		// array_merge() will remove numeric keys (here config keys), so addition arrays instead
231
-		$values = $this->formatAppValues($app, ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []));
232
-		$values = array_filter(
233
-			$values,
234
-			function (string $key) use ($prefix): bool {
235
-				return str_starts_with($key, $prefix); // filter values based on $prefix
236
-			}, ARRAY_FILTER_USE_KEY
237
-		);
238
-
239
-		if (!$filtered) {
240
-			return $values;
241
-		}
242
-
243
-		/**
244
-		 * Using the old (deprecated) list of sensitive values.
245
-		 */
246
-		foreach ($this->getSensitiveKeys($app) as $sensitiveKeyExp) {
247
-			$sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
248
-			foreach ($sensitiveKeys as $sensitiveKey) {
249
-				$this->valueTypes[$app][$sensitiveKey] = ($this->valueTypes[$app][$sensitiveKey] ?? 0) | self::VALUE_SENSITIVE;
250
-			}
251
-		}
252
-
253
-		$result = [];
254
-		foreach ($values as $key => $value) {
255
-			$result[$key] = $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key] ?? 0) ? IConfig::SENSITIVE_VALUE : $value;
256
-		}
257
-
258
-		return $result;
259
-	}
260
-
261
-	/**
262
-	 * @inheritDoc
263
-	 *
264
-	 * @param string $key config key
265
-	 * @param bool $lazy search within lazy loaded config
266
-	 * @param int|null $typedAs enforce type for the returned values ({@see self::VALUE_STRING} and others)
267
-	 *
268
-	 * @return array<string, string|int|float|bool|array> [appId => configValue]
269
-	 * @since 29.0.0
270
-	 */
271
-	public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array {
272
-		$this->assertParams('', $key, true);
273
-		$this->loadConfig(null, $lazy);
274
-
275
-		/** @var array<array-key, array<array-key, mixed>> $cache */
276
-		if ($lazy) {
277
-			$cache = $this->lazyCache;
278
-		} else {
279
-			$cache = $this->fastCache;
280
-		}
281
-
282
-		$values = [];
283
-		foreach (array_keys($cache) as $app) {
284
-			if (isset($cache[$app][$key])) {
285
-				$values[$app] = $this->convertTypedValue($cache[$app][$key], $typedAs ?? $this->getValueType((string)$app, $key, $lazy));
286
-			}
287
-		}
288
-
289
-		return $values;
290
-	}
291
-
292
-
293
-	/**
294
-	 * Get the config value as string.
295
-	 * If the value does not exist the given default will be returned.
296
-	 *
297
-	 * Set lazy to `null` to ignore it and get the value from either source.
298
-	 *
299
-	 * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
300
-	 *
301
-	 * @param string $app id of the app
302
-	 * @param string $key config key
303
-	 * @param string $default config value
304
-	 * @param null|bool $lazy get config as lazy loaded or not. can be NULL
305
-	 *
306
-	 * @return string the value or $default
307
-	 * @internal
308
-	 * @since 29.0.0
309
-	 * @see IAppConfig for explanation about lazy loading
310
-	 * @see getValueString()
311
-	 * @see getValueInt()
312
-	 * @see getValueFloat()
313
-	 * @see getValueBool()
314
-	 * @see getValueArray()
315
-	 */
316
-	public function getValueMixed(
317
-		string $app,
318
-		string $key,
319
-		string $default = '',
320
-		?bool $lazy = false,
321
-	): string {
322
-		try {
323
-			$lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
324
-		} catch (AppConfigUnknownKeyException) {
325
-			return $default;
326
-		}
327
-
328
-		return $this->getTypedValue(
329
-			$app,
330
-			$key,
331
-			$default,
332
-			$lazy,
333
-			self::VALUE_MIXED
334
-		);
335
-	}
336
-
337
-	/**
338
-	 * @inheritDoc
339
-	 *
340
-	 * @param string $app id of the app
341
-	 * @param string $key config key
342
-	 * @param string $default default value
343
-	 * @param bool $lazy search within lazy loaded config
344
-	 *
345
-	 * @return string stored config value or $default if not set in database
346
-	 * @throws InvalidArgumentException if one of the argument format is invalid
347
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
348
-	 * @since 29.0.0
349
-	 * @see IAppConfig for explanation about lazy loading
350
-	 */
351
-	public function getValueString(
352
-		string $app,
353
-		string $key,
354
-		string $default = '',
355
-		bool $lazy = false,
356
-	): string {
357
-		return $this->getTypedValue($app, $key, $default, $lazy, self::VALUE_STRING);
358
-	}
359
-
360
-	/**
361
-	 * @inheritDoc
362
-	 *
363
-	 * @param string $app id of the app
364
-	 * @param string $key config key
365
-	 * @param int $default default value
366
-	 * @param bool $lazy search within lazy loaded config
367
-	 *
368
-	 * @return int stored config value or $default if not set in database
369
-	 * @throws InvalidArgumentException if one of the argument format is invalid
370
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
371
-	 * @since 29.0.0
372
-	 * @see IAppConfig for explanation about lazy loading
373
-	 */
374
-	public function getValueInt(
375
-		string $app,
376
-		string $key,
377
-		int $default = 0,
378
-		bool $lazy = false,
379
-	): int {
380
-		return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
381
-	}
382
-
383
-	/**
384
-	 * @inheritDoc
385
-	 *
386
-	 * @param string $app id of the app
387
-	 * @param string $key config key
388
-	 * @param float $default default value
389
-	 * @param bool $lazy search within lazy loaded config
390
-	 *
391
-	 * @return float stored config value or $default if not set in database
392
-	 * @throws InvalidArgumentException if one of the argument format is invalid
393
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
394
-	 * @since 29.0.0
395
-	 * @see IAppConfig for explanation about lazy loading
396
-	 */
397
-	public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
398
-		return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
399
-	}
400
-
401
-	/**
402
-	 * @inheritDoc
403
-	 *
404
-	 * @param string $app id of the app
405
-	 * @param string $key config key
406
-	 * @param bool $default default value
407
-	 * @param bool $lazy search within lazy loaded config
408
-	 *
409
-	 * @return bool stored config value or $default if not set in database
410
-	 * @throws InvalidArgumentException if one of the argument format is invalid
411
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
412
-	 * @since 29.0.0
413
-	 * @see IAppConfig for explanation about lazy loading
414
-	 */
415
-	public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
416
-		$b = strtolower($this->getTypedValue($app, $key, $default ? 'true' : 'false', $lazy, self::VALUE_BOOL));
417
-		return in_array($b, ['1', 'true', 'yes', 'on']);
418
-	}
419
-
420
-	/**
421
-	 * @inheritDoc
422
-	 *
423
-	 * @param string $app id of the app
424
-	 * @param string $key config key
425
-	 * @param array $default default value
426
-	 * @param bool $lazy search within lazy loaded config
427
-	 *
428
-	 * @return array stored config value or $default if not set in database
429
-	 * @throws InvalidArgumentException if one of the argument format is invalid
430
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
431
-	 * @since 29.0.0
432
-	 * @see IAppConfig for explanation about lazy loading
433
-	 */
434
-	public function getValueArray(
435
-		string $app,
436
-		string $key,
437
-		array $default = [],
438
-		bool $lazy = false,
439
-	): array {
440
-		try {
441
-			$defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
442
-			$value = json_decode($this->getTypedValue($app, $key, $defaultJson, $lazy, self::VALUE_ARRAY), true, flags: JSON_THROW_ON_ERROR);
443
-
444
-			return is_array($value) ? $value : [];
445
-		} catch (JsonException) {
446
-			return [];
447
-		}
448
-	}
449
-
450
-	/**
451
-	 * @param string $app id of the app
452
-	 * @param string $key config key
453
-	 * @param string $default default value
454
-	 * @param bool $lazy search within lazy loaded config
455
-	 * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT}{@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
456
-	 *
457
-	 * @return string
458
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
459
-	 * @throws InvalidArgumentException
460
-	 */
461
-	private function getTypedValue(
462
-		string $app,
463
-		string $key,
464
-		string $default,
465
-		bool $lazy,
466
-		int $type,
467
-	): string {
468
-		$this->assertParams($app, $key, valueType: $type);
469
-		$origKey = $key;
470
-		$matched = $this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default);
471
-		if ($default === null) {
472
-			// there is no logical reason for it to be null
473
-			throw new \Exception('default cannot be null');
474
-		}
475
-
476
-		// returns default if strictness of lexicon is set to WARNING (block and report)
477
-		if (!$matched) {
478
-			return $default;
479
-		}
480
-
481
-		$this->loadConfig($app, $lazy);
482
-
483
-		/**
484
-		 * We ignore check if mixed type is requested.
485
-		 * If type of stored value is set as mixed, we don't filter.
486
-		 * If type of stored value is defined, we compare with the one requested.
487
-		 */
488
-		$knownType = $this->valueTypes[$app][$key] ?? 0;
489
-		if (!$this->isTyped(self::VALUE_MIXED, $type)
490
-			&& $knownType > 0
491
-			&& !$this->isTyped(self::VALUE_MIXED, $knownType)
492
-			&& !$this->isTyped($type, $knownType)) {
493
-			$this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
494
-			throw new AppConfigTypeConflictException('conflict with value type from database');
495
-		}
496
-
497
-		/**
498
-		 * - the pair $app/$key cannot exist in both array,
499
-		 * - we should still return an existing non-lazy value even if current method
500
-		 *   is called with $lazy is true
501
-		 *
502
-		 * This way, lazyCache will be empty until the load for lazy config value is requested.
503
-		 */
504
-		if (isset($this->lazyCache[$app][$key])) {
505
-			$value = $this->lazyCache[$app][$key];
506
-		} elseif (isset($this->fastCache[$app][$key])) {
507
-			$value = $this->fastCache[$app][$key];
508
-		} else {
509
-			return $default;
510
-		}
511
-
512
-		$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $knownType);
513
-		if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
514
-			// Only decrypt values that are stored encrypted
515
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
516
-		}
517
-
518
-		// in case the key was modified while running matchAndApplyLexiconDefinition() we are
519
-		// interested to check options in case a modification of the value is needed
520
-		// ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
521
-		if ($origKey !== $key && $type === self::VALUE_BOOL) {
522
-			$configManager = Server::get(ConfigManager::class);
523
-			$value = ($configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
524
-		}
525
-
526
-		return $value;
527
-	}
528
-
529
-	/**
530
-	 * @inheritDoc
531
-	 *
532
-	 * @param string $app id of the app
533
-	 * @param string $key config key
534
-	 *
535
-	 * @return int type of the value
536
-	 * @throws AppConfigUnknownKeyException if config key is not known
537
-	 * @since 29.0.0
538
-	 * @see VALUE_STRING
539
-	 * @see VALUE_INT
540
-	 * @see VALUE_FLOAT
541
-	 * @see VALUE_BOOL
542
-	 * @see VALUE_ARRAY
543
-	 */
544
-	public function getValueType(string $app, string $key, ?bool $lazy = null): int {
545
-		$type = self::VALUE_MIXED;
546
-		$ignorable = $lazy ?? false;
547
-		$this->matchAndApplyLexiconDefinition($app, $key, $ignorable, $type);
548
-		if ($type !== self::VALUE_MIXED) {
549
-			// a modified $type means config key is set in Lexicon
550
-			return $type;
551
-		}
552
-
553
-		$this->assertParams($app, $key);
554
-		$this->loadConfig($app, $lazy);
555
-
556
-		if (!isset($this->valueTypes[$app][$key])) {
557
-			throw new AppConfigUnknownKeyException('unknown config key');
558
-		}
559
-
560
-		$type = $this->valueTypes[$app][$key];
561
-		$type &= ~self::VALUE_SENSITIVE;
562
-		return $type;
563
-	}
564
-
565
-
566
-	/**
567
-	 * Store a config key and its value in database as VALUE_MIXED
568
-	 *
569
-	 * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
570
-	 *
571
-	 * @param string $app id of the app
572
-	 * @param string $key config key
573
-	 * @param string $value config value
574
-	 * @param bool $lazy set config as lazy loaded
575
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
576
-	 *
577
-	 * @return bool TRUE if value was different, therefor updated in database
578
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED
579
-	 * @internal
580
-	 * @since 29.0.0
581
-	 * @see IAppConfig for explanation about lazy loading
582
-	 * @see setValueString()
583
-	 * @see setValueInt()
584
-	 * @see setValueFloat()
585
-	 * @see setValueBool()
586
-	 * @see setValueArray()
587
-	 */
588
-	public function setValueMixed(
589
-		string $app,
590
-		string $key,
591
-		string $value,
592
-		bool $lazy = false,
593
-		bool $sensitive = false,
594
-	): bool {
595
-		return $this->setTypedValue(
596
-			$app,
597
-			$key,
598
-			$value,
599
-			$lazy,
600
-			self::VALUE_MIXED | ($sensitive ? self::VALUE_SENSITIVE : 0)
601
-		);
602
-	}
603
-
604
-
605
-	/**
606
-	 * @inheritDoc
607
-	 *
608
-	 * @param string $app id of the app
609
-	 * @param string $key config key
610
-	 * @param string $value config value
611
-	 * @param bool $lazy set config as lazy loaded
612
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
613
-	 *
614
-	 * @return bool TRUE if value was different, therefor updated in database
615
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
616
-	 * @since 29.0.0
617
-	 * @see IAppConfig for explanation about lazy loading
618
-	 */
619
-	public function setValueString(
620
-		string $app,
621
-		string $key,
622
-		string $value,
623
-		bool $lazy = false,
624
-		bool $sensitive = false,
625
-	): bool {
626
-		return $this->setTypedValue(
627
-			$app,
628
-			$key,
629
-			$value,
630
-			$lazy,
631
-			self::VALUE_STRING | ($sensitive ? self::VALUE_SENSITIVE : 0)
632
-		);
633
-	}
634
-
635
-	/**
636
-	 * @inheritDoc
637
-	 *
638
-	 * @param string $app id of the app
639
-	 * @param string $key config key
640
-	 * @param int $value config value
641
-	 * @param bool $lazy set config as lazy loaded
642
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
643
-	 *
644
-	 * @return bool TRUE if value was different, therefor updated in database
645
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
646
-	 * @since 29.0.0
647
-	 * @see IAppConfig for explanation about lazy loading
648
-	 */
649
-	public function setValueInt(
650
-		string $app,
651
-		string $key,
652
-		int $value,
653
-		bool $lazy = false,
654
-		bool $sensitive = false,
655
-	): bool {
656
-		if ($value > 2000000000) {
657
-			$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.');
658
-		}
659
-
660
-		return $this->setTypedValue(
661
-			$app,
662
-			$key,
663
-			(string)$value,
664
-			$lazy,
665
-			self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
666
-		);
667
-	}
668
-
669
-	/**
670
-	 * @inheritDoc
671
-	 *
672
-	 * @param string $app id of the app
673
-	 * @param string $key config key
674
-	 * @param float $value config value
675
-	 * @param bool $lazy set config as lazy loaded
676
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
677
-	 *
678
-	 * @return bool TRUE if value was different, therefor updated in database
679
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
680
-	 * @since 29.0.0
681
-	 * @see IAppConfig for explanation about lazy loading
682
-	 */
683
-	public function setValueFloat(
684
-		string $app,
685
-		string $key,
686
-		float $value,
687
-		bool $lazy = false,
688
-		bool $sensitive = false,
689
-	): bool {
690
-		return $this->setTypedValue(
691
-			$app,
692
-			$key,
693
-			(string)$value,
694
-			$lazy,
695
-			self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
696
-		);
697
-	}
698
-
699
-	/**
700
-	 * @inheritDoc
701
-	 *
702
-	 * @param string $app id of the app
703
-	 * @param string $key config key
704
-	 * @param bool $value config value
705
-	 * @param bool $lazy set config as lazy loaded
706
-	 *
707
-	 * @return bool TRUE if value was different, therefor updated in database
708
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
709
-	 * @since 29.0.0
710
-	 * @see IAppConfig for explanation about lazy loading
711
-	 */
712
-	public function setValueBool(
713
-		string $app,
714
-		string $key,
715
-		bool $value,
716
-		bool $lazy = false,
717
-	): bool {
718
-		return $this->setTypedValue(
719
-			$app,
720
-			$key,
721
-			($value) ? '1' : '0',
722
-			$lazy,
723
-			self::VALUE_BOOL
724
-		);
725
-	}
726
-
727
-	/**
728
-	 * @inheritDoc
729
-	 *
730
-	 * @param string $app id of the app
731
-	 * @param string $key config key
732
-	 * @param array $value config value
733
-	 * @param bool $lazy set config as lazy loaded
734
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
735
-	 *
736
-	 * @return bool TRUE if value was different, therefor updated in database
737
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
738
-	 * @throws JsonException
739
-	 * @since 29.0.0
740
-	 * @see IAppConfig for explanation about lazy loading
741
-	 */
742
-	public function setValueArray(
743
-		string $app,
744
-		string $key,
745
-		array $value,
746
-		bool $lazy = false,
747
-		bool $sensitive = false,
748
-	): bool {
749
-		try {
750
-			return $this->setTypedValue(
751
-				$app,
752
-				$key,
753
-				json_encode($value, JSON_THROW_ON_ERROR),
754
-				$lazy,
755
-				self::VALUE_ARRAY | ($sensitive ? self::VALUE_SENSITIVE : 0)
756
-			);
757
-		} catch (JsonException $e) {
758
-			$this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
759
-			throw $e;
760
-		}
761
-	}
762
-
763
-	/**
764
-	 * Store a config key and its value in database
765
-	 *
766
-	 * If config key is already known with the exact same config value and same sensitive/lazy status, the
767
-	 * database is not updated. If config value was previously stored as sensitive, status will not be
768
-	 * altered.
769
-	 *
770
-	 * @param string $app id of the app
771
-	 * @param string $key config key
772
-	 * @param string $value config value
773
-	 * @param bool $lazy config set as lazy loaded
774
-	 * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
775
-	 *
776
-	 * @return bool TRUE if value was updated in database
777
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
778
-	 * @see IAppConfig for explanation about lazy loading
779
-	 */
780
-	private function setTypedValue(
781
-		string $app,
782
-		string $key,
783
-		string $value,
784
-		bool $lazy,
785
-		int $type,
786
-	): bool {
787
-		$this->assertParams($app, $key);
788
-		if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
789
-			return false; // returns false as database is not updated
790
-		}
791
-		$this->loadConfig(null, $lazy);
792
-
793
-		$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
794
-		$inserted = $refreshCache = false;
795
-
796
-		$origValue = $value;
797
-		if ($sensitive || ($this->hasKey($app, $key, $lazy) && $this->isSensitive($app, $key, $lazy))) {
798
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
799
-		}
800
-
801
-		if ($this->hasKey($app, $key, $lazy)) {
802
-			/**
803
-			 * no update if key is already known with set lazy status and value is
804
-			 * not different, unless sensitivity is switched from false to true.
805
-			 */
806
-			if ($origValue === $this->getTypedValue($app, $key, $value, $lazy, $type)
807
-				&& (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
808
-				return false;
809
-			}
810
-		} else {
811
-			/**
812
-			 * if key is not known yet, we try to insert.
813
-			 * It might fail if the key exists with a different lazy flag.
814
-			 */
815
-			try {
816
-				$insert = $this->connection->getQueryBuilder();
817
-				$insert->insert('appconfig')
818
-					->setValue('appid', $insert->createNamedParameter($app))
819
-					->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
820
-					->setValue('type', $insert->createNamedParameter($type, IQueryBuilder::PARAM_INT))
821
-					->setValue('configkey', $insert->createNamedParameter($key))
822
-					->setValue('configvalue', $insert->createNamedParameter($value));
823
-				$insert->executeStatement();
824
-				$inserted = true;
825
-			} catch (DBException $e) {
826
-				if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
827
-					throw $e; // TODO: throw exception or just log and returns false !?
828
-				}
829
-			}
830
-		}
831
-
832
-		/**
833
-		 * We cannot insert a new row, meaning we need to update an already existing one
834
-		 */
835
-		if (!$inserted) {
836
-			$currType = $this->valueTypes[$app][$key] ?? 0;
837
-			if ($currType === 0) { // this might happen when switching lazy loading status
838
-				$this->loadConfigAll();
839
-				$currType = $this->valueTypes[$app][$key] ?? 0;
840
-			}
841
-
842
-			/**
843
-			 * This should only happen during the upgrade process from 28 to 29.
844
-			 * We only log a warning and set it to VALUE_MIXED.
845
-			 */
846
-			if ($currType === 0) {
847
-				$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]);
848
-				$currType = self::VALUE_MIXED;
849
-			}
850
-
851
-			/**
852
-			 * we only accept a different type from the one stored in database
853
-			 * if the one stored in database is not-defined (VALUE_MIXED)
854
-			 */
855
-			if (!$this->isTyped(self::VALUE_MIXED, $currType)
856
-				&& ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
857
-				try {
858
-					$currType = $this->convertTypeToString($currType);
859
-					$type = $this->convertTypeToString($type);
860
-				} catch (AppConfigIncorrectTypeException) {
861
-					// can be ignored, this was just needed for a better exception message.
862
-				}
863
-				throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
864
-			}
865
-
866
-			// we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
867
-			if ($sensitive || $this->isTyped(self::VALUE_SENSITIVE, $currType)) {
868
-				$type |= self::VALUE_SENSITIVE;
869
-			}
870
-
871
-			if ($lazy !== $this->isLazy($app, $key)) {
872
-				$refreshCache = true;
873
-			}
874
-
875
-			$update = $this->connection->getQueryBuilder();
876
-			$update->update('appconfig')
877
-				->set('configvalue', $update->createNamedParameter($value))
878
-				->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
879
-				->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
880
-				->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
881
-				->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
882
-
883
-			$update->executeStatement();
884
-		}
885
-
886
-		if ($refreshCache) {
887
-			$this->clearCache();
888
-			return true;
889
-		}
890
-
891
-		// update local cache
892
-		if ($lazy) {
893
-			$this->lazyCache[$app][$key] = $value;
894
-		} else {
895
-			$this->fastCache[$app][$key] = $value;
896
-		}
897
-		$this->valueTypes[$app][$key] = $type;
898
-
899
-		return true;
900
-	}
901
-
902
-	/**
903
-	 * Change the type of config value.
904
-	 *
905
-	 * **WARNING:** Method is internal and **MUST** not be used as it may break things.
906
-	 *
907
-	 * @param string $app id of the app
908
-	 * @param string $key config key
909
-	 * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
910
-	 *
911
-	 * @return bool TRUE if database update were necessary
912
-	 * @throws AppConfigUnknownKeyException if $key is now known in database
913
-	 * @throws AppConfigIncorrectTypeException if $type is not valid
914
-	 * @internal
915
-	 * @since 29.0.0
916
-	 */
917
-	public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
918
-		$this->assertParams($app, $key);
919
-		$this->loadConfigAll();
920
-		$this->matchAndApplyLexiconDefinition($app, $key);
921
-		$this->isLazy($app, $key); // confirm key exists
922
-
923
-		// type can only be one type
924
-		if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
925
-			throw new AppConfigIncorrectTypeException('Unknown value type');
926
-		}
927
-
928
-		$currType = $this->valueTypes[$app][$key];
929
-		if (($type | self::VALUE_SENSITIVE) === ($currType | self::VALUE_SENSITIVE)) {
930
-			return false;
931
-		}
932
-
933
-		// we complete with sensitive flag if the stored value is set as sensitive
934
-		if ($this->isTyped(self::VALUE_SENSITIVE, $currType)) {
935
-			$type = $type | self::VALUE_SENSITIVE;
936
-		}
937
-
938
-		$update = $this->connection->getQueryBuilder();
939
-		$update->update('appconfig')
940
-			->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
941
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
942
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
943
-		$update->executeStatement();
944
-		$this->valueTypes[$app][$key] = $type;
945
-
946
-		return true;
947
-	}
948
-
949
-
950
-	/**
951
-	 * @inheritDoc
952
-	 *
953
-	 * @param string $app id of the app
954
-	 * @param string $key config key
955
-	 * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
956
-	 *
957
-	 * @return bool TRUE if entry was found in database and an update was necessary
958
-	 * @since 29.0.0
959
-	 */
960
-	public function updateSensitive(string $app, string $key, bool $sensitive): bool {
961
-		$this->assertParams($app, $key);
962
-		$this->loadConfigAll();
963
-		$this->matchAndApplyLexiconDefinition($app, $key);
964
-
965
-		try {
966
-			if ($sensitive === $this->isSensitive($app, $key, null)) {
967
-				return false;
968
-			}
969
-		} catch (AppConfigUnknownKeyException $e) {
970
-			return false;
971
-		}
972
-
973
-		$lazy = $this->isLazy($app, $key);
974
-		if ($lazy) {
975
-			$cache = $this->lazyCache;
976
-		} else {
977
-			$cache = $this->fastCache;
978
-		}
979
-
980
-		if (!isset($cache[$app][$key])) {
981
-			throw new AppConfigUnknownKeyException('unknown config key');
982
-		}
983
-
984
-		/**
985
-		 * type returned by getValueType() is already cleaned from sensitive flag
986
-		 * we just need to update it based on $sensitive and store it in database
987
-		 */
988
-		$type = $this->getValueType($app, $key);
989
-		$value = $cache[$app][$key];
990
-		if ($sensitive) {
991
-			$type |= self::VALUE_SENSITIVE;
992
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
993
-		} else {
994
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
995
-		}
996
-
997
-		$update = $this->connection->getQueryBuilder();
998
-		$update->update('appconfig')
999
-			->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
1000
-			->set('configvalue', $update->createNamedParameter($value))
1001
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1002
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1003
-		$update->executeStatement();
1004
-
1005
-		$this->valueTypes[$app][$key] = $type;
1006
-
1007
-		return true;
1008
-	}
1009
-
1010
-	/**
1011
-	 * @inheritDoc
1012
-	 *
1013
-	 * @param string $app id of the app
1014
-	 * @param string $key config key
1015
-	 * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
1016
-	 *
1017
-	 * @return bool TRUE if entry was found in database and an update was necessary
1018
-	 * @since 29.0.0
1019
-	 */
1020
-	public function updateLazy(string $app, string $key, bool $lazy): bool {
1021
-		$this->assertParams($app, $key);
1022
-		$this->loadConfigAll();
1023
-		$this->matchAndApplyLexiconDefinition($app, $key);
1024
-
1025
-		try {
1026
-			if ($lazy === $this->isLazy($app, $key)) {
1027
-				return false;
1028
-			}
1029
-		} catch (AppConfigUnknownKeyException $e) {
1030
-			return false;
1031
-		}
1032
-
1033
-		$update = $this->connection->getQueryBuilder();
1034
-		$update->update('appconfig')
1035
-			->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
1036
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1037
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1038
-		$update->executeStatement();
1039
-
1040
-		// At this point, it is a lot safer to clean cache
1041
-		$this->clearCache();
1042
-
1043
-		return true;
1044
-	}
1045
-
1046
-	/**
1047
-	 * @inheritDoc
1048
-	 *
1049
-	 * @param string $app id of the app
1050
-	 * @param string $key config key
1051
-	 *
1052
-	 * @return array
1053
-	 * @throws AppConfigUnknownKeyException if config key is not known in database
1054
-	 * @since 29.0.0
1055
-	 */
1056
-	public function getDetails(string $app, string $key): array {
1057
-		$this->assertParams($app, $key);
1058
-		$this->loadConfigAll();
1059
-		$this->matchAndApplyLexiconDefinition($app, $key);
1060
-		$lazy = $this->isLazy($app, $key);
1061
-
1062
-		if ($lazy) {
1063
-			$cache = $this->lazyCache;
1064
-		} else {
1065
-			$cache = $this->fastCache;
1066
-		}
1067
-
1068
-		$type = $this->getValueType($app, $key);
1069
-		try {
1070
-			$typeString = $this->convertTypeToString($type);
1071
-		} catch (AppConfigIncorrectTypeException $e) {
1072
-			$this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
1073
-			$typeString = (string)$type;
1074
-		}
1075
-
1076
-		if (!isset($cache[$app][$key])) {
1077
-			throw new AppConfigUnknownKeyException('unknown config key');
1078
-		}
1079
-
1080
-		$value = $cache[$app][$key];
1081
-		$sensitive = $this->isSensitive($app, $key, null);
1082
-		if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
1083
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1084
-		}
1085
-
1086
-		return [
1087
-			'app' => $app,
1088
-			'key' => $key,
1089
-			'value' => $value,
1090
-			'type' => $type,
1091
-			'lazy' => $lazy,
1092
-			'typeString' => $typeString,
1093
-			'sensitive' => $sensitive
1094
-		];
1095
-	}
1096
-
1097
-	/**
1098
-	 * @param string $type
1099
-	 *
1100
-	 * @return int
1101
-	 * @throws AppConfigIncorrectTypeException
1102
-	 * @since 29.0.0
1103
-	 */
1104
-	public function convertTypeToInt(string $type): int {
1105
-		return match (strtolower($type)) {
1106
-			'mixed' => IAppConfig::VALUE_MIXED,
1107
-			'string' => IAppConfig::VALUE_STRING,
1108
-			'integer' => IAppConfig::VALUE_INT,
1109
-			'float' => IAppConfig::VALUE_FLOAT,
1110
-			'boolean' => IAppConfig::VALUE_BOOL,
1111
-			'array' => IAppConfig::VALUE_ARRAY,
1112
-			default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
1113
-		};
1114
-	}
1115
-
1116
-	/**
1117
-	 * @param int $type
1118
-	 *
1119
-	 * @return string
1120
-	 * @throws AppConfigIncorrectTypeException
1121
-	 * @since 29.0.0
1122
-	 */
1123
-	public function convertTypeToString(int $type): string {
1124
-		$type &= ~self::VALUE_SENSITIVE;
1125
-
1126
-		return match ($type) {
1127
-			IAppConfig::VALUE_MIXED => 'mixed',
1128
-			IAppConfig::VALUE_STRING => 'string',
1129
-			IAppConfig::VALUE_INT => 'integer',
1130
-			IAppConfig::VALUE_FLOAT => 'float',
1131
-			IAppConfig::VALUE_BOOL => 'boolean',
1132
-			IAppConfig::VALUE_ARRAY => 'array',
1133
-			default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
1134
-		};
1135
-	}
1136
-
1137
-	/**
1138
-	 * @inheritDoc
1139
-	 *
1140
-	 * @param string $app id of the app
1141
-	 * @param string $key config key
1142
-	 *
1143
-	 * @since 29.0.0
1144
-	 */
1145
-	public function deleteKey(string $app, string $key): void {
1146
-		$this->assertParams($app, $key);
1147
-		$this->matchAndApplyLexiconDefinition($app, $key);
1148
-
1149
-		$qb = $this->connection->getQueryBuilder();
1150
-		$qb->delete('appconfig')
1151
-			->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1152
-			->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1153
-		$qb->executeStatement();
1154
-
1155
-		unset($this->lazyCache[$app][$key]);
1156
-		unset($this->fastCache[$app][$key]);
1157
-		unset($this->valueTypes[$app][$key]);
1158
-	}
1159
-
1160
-	/**
1161
-	 * @inheritDoc
1162
-	 *
1163
-	 * @param string $app id of the app
1164
-	 *
1165
-	 * @since 29.0.0
1166
-	 */
1167
-	public function deleteApp(string $app): void {
1168
-		$this->assertParams($app);
1169
-		$qb = $this->connection->getQueryBuilder();
1170
-		$qb->delete('appconfig')
1171
-			->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
1172
-		$qb->executeStatement();
1173
-
1174
-		$this->clearCache();
1175
-	}
1176
-
1177
-	/**
1178
-	 * @inheritDoc
1179
-	 *
1180
-	 * @param bool $reload set to TRUE to refill cache instantly after clearing it
1181
-	 *
1182
-	 * @since 29.0.0
1183
-	 */
1184
-	public function clearCache(bool $reload = false): void {
1185
-		$this->lazyLoaded = $this->fastLoaded = false;
1186
-		$this->lazyCache = $this->fastCache = $this->valueTypes = $this->configLexiconDetails = [];
1187
-		$this->configLexiconPreset = null;
1188
-
1189
-		if (!$reload) {
1190
-			return;
1191
-		}
1192
-
1193
-		$this->loadConfigAll();
1194
-	}
1195
-
1196
-
1197
-	/**
1198
-	 * For debug purpose.
1199
-	 * Returns the cached data.
1200
-	 *
1201
-	 * @return array
1202
-	 * @since 29.0.0
1203
-	 * @internal
1204
-	 */
1205
-	public function statusCache(): array {
1206
-		return [
1207
-			'fastLoaded' => $this->fastLoaded,
1208
-			'fastCache' => $this->fastCache,
1209
-			'lazyLoaded' => $this->lazyLoaded,
1210
-			'lazyCache' => $this->lazyCache,
1211
-		];
1212
-	}
1213
-
1214
-	/**
1215
-	 * @param int $needle bitflag to search
1216
-	 * @param int $type known value
1217
-	 *
1218
-	 * @return bool TRUE if bitflag $needle is set in $type
1219
-	 */
1220
-	private function isTyped(int $needle, int $type): bool {
1221
-		return (($needle & $type) !== 0);
1222
-	}
1223
-
1224
-	/**
1225
-	 * Confirm the string set for app and key fit the database description
1226
-	 *
1227
-	 * @param string $app assert $app fit in database
1228
-	 * @param string $configKey assert config key fit in database
1229
-	 * @param bool $allowEmptyApp $app can be empty string
1230
-	 * @param int $valueType assert value type is only one type
1231
-	 *
1232
-	 * @throws InvalidArgumentException
1233
-	 */
1234
-	private function assertParams(string $app = '', string $configKey = '', bool $allowEmptyApp = false, int $valueType = -1): void {
1235
-		if (!$allowEmptyApp && $app === '') {
1236
-			throw new InvalidArgumentException('app cannot be an empty string');
1237
-		}
1238
-		if (strlen($app) > self::APP_MAX_LENGTH) {
1239
-			throw new InvalidArgumentException(
1240
-				'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
1241
-			);
1242
-		}
1243
-		if (strlen($configKey) > self::KEY_MAX_LENGTH) {
1244
-			throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
1245
-		}
1246
-		if ($valueType > -1) {
1247
-			$valueType &= ~self::VALUE_SENSITIVE;
1248
-			if (!in_array($valueType, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
1249
-				throw new InvalidArgumentException('Unknown value type');
1250
-			}
1251
-		}
1252
-	}
1253
-
1254
-	private function loadConfigAll(?string $app = null): void {
1255
-		$this->loadConfig($app, null);
1256
-	}
1257
-
1258
-	/**
1259
-	 * Load normal config or config set as lazy loaded
1260
-	 *
1261
-	 * @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
1262
-	 */
1263
-	private function loadConfig(?string $app = null, ?bool $lazy = false): void {
1264
-		if ($this->isLoaded($lazy)) {
1265
-			return;
1266
-		}
1267
-
1268
-		// if lazy is null or true, we debug log
1269
-		if (($lazy ?? true) !== false && $app !== null) {
1270
-			$exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "' . $app . '"');
1271
-			$this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
1272
-		}
1273
-
1274
-		$qb = $this->connection->getQueryBuilder();
1275
-		$qb->from('appconfig');
1276
-
1277
-		// we only need value from lazy when loadConfig does not specify it
1278
-		$qb->select('appid', 'configkey', 'configvalue', 'type');
1279
-
1280
-		if ($lazy !== null) {
1281
-			$qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
1282
-		} else {
1283
-			$qb->addSelect('lazy');
1284
-		}
1285
-
1286
-		$result = $qb->executeQuery();
1287
-		$rows = $result->fetchAll();
1288
-		foreach ($rows as $row) {
1289
-			// most of the time, 'lazy' is not in the select because its value is already known
1290
-			if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
1291
-				$this->lazyCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1292
-			} else {
1293
-				$this->fastCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1294
-			}
1295
-			$this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
1296
-		}
1297
-		$result->closeCursor();
1298
-		$this->setAsLoaded($lazy);
1299
-	}
1300
-
1301
-	/**
1302
-	 * if $lazy is:
1303
-	 *  - false: will returns true if fast config is loaded
1304
-	 *  - true : will returns true if lazy config is loaded
1305
-	 *  - null : will returns true if both config are loaded
1306
-	 *
1307
-	 * @param bool $lazy
1308
-	 *
1309
-	 * @return bool
1310
-	 */
1311
-	private function isLoaded(?bool $lazy): bool {
1312
-		if ($lazy === null) {
1313
-			return $this->lazyLoaded && $this->fastLoaded;
1314
-		}
1315
-
1316
-		return $lazy ? $this->lazyLoaded : $this->fastLoaded;
1317
-	}
1318
-
1319
-	/**
1320
-	 * if $lazy is:
1321
-	 * - false: set fast config as loaded
1322
-	 * - true : set lazy config as loaded
1323
-	 * - null : set both config as loaded
1324
-	 *
1325
-	 * @param bool $lazy
1326
-	 */
1327
-	private function setAsLoaded(?bool $lazy): void {
1328
-		if ($lazy === null) {
1329
-			$this->fastLoaded = true;
1330
-			$this->lazyLoaded = true;
1331
-
1332
-			return;
1333
-		}
1334
-
1335
-		if ($lazy) {
1336
-			$this->lazyLoaded = true;
1337
-		} else {
1338
-			$this->fastLoaded = true;
1339
-		}
1340
-	}
1341
-
1342
-	/**
1343
-	 * Gets the config value
1344
-	 *
1345
-	 * @param string $app app
1346
-	 * @param string $key key
1347
-	 * @param string $default = null, default value if the key does not exist
1348
-	 *
1349
-	 * @return string the value or $default
1350
-	 * @deprecated 29.0.0 use getValue*()
1351
-	 *
1352
-	 * This function gets a value from the appconfig table. If the key does
1353
-	 * not exist the default value will be returned
1354
-	 */
1355
-	public function getValue($app, $key, $default = null) {
1356
-		$this->loadConfig($app);
1357
-		$this->matchAndApplyLexiconDefinition($app, $key);
1358
-
1359
-		return $this->fastCache[$app][$key] ?? $default;
1360
-	}
1361
-
1362
-	/**
1363
-	 * Sets a value. If the key did not exist before it will be created.
1364
-	 *
1365
-	 * @param string $app app
1366
-	 * @param string $key key
1367
-	 * @param string|float|int $value value
1368
-	 *
1369
-	 * @return bool True if the value was inserted or updated, false if the value was the same
1370
-	 * @throws AppConfigTypeConflictException
1371
-	 * @throws AppConfigUnknownKeyException
1372
-	 * @deprecated 29.0.0
1373
-	 */
1374
-	public function setValue($app, $key, $value) {
1375
-		/**
1376
-		 * TODO: would it be overkill, or decently improve performance, to catch
1377
-		 * call to this method with $key='enabled' and 'hide' config value related
1378
-		 * to $app when the app is disabled (by modifying entry in database: lazy=lazy+2)
1379
-		 * or enabled (lazy=lazy-2)
1380
-		 *
1381
-		 * this solution would remove the loading of config values from disabled app
1382
-		 * unless calling the method {@see loadConfigAll()}
1383
-		 */
1384
-		return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
1385
-	}
1386
-
1387
-
1388
-	/**
1389
-	 * get multiple values, either the app or key can be used as wildcard by setting it to false
1390
-	 *
1391
-	 * @param string|false $app
1392
-	 * @param string|false $key
1393
-	 *
1394
-	 * @return array|false
1395
-	 * @deprecated 29.0.0 use {@see getAllValues()}
1396
-	 */
1397
-	public function getValues($app, $key) {
1398
-		if (($app !== false) === ($key !== false)) {
1399
-			return false;
1400
-		}
1401
-
1402
-		$key = ($key === false) ? '' : $key;
1403
-		if (!$app) {
1404
-			return $this->searchValues($key, false, self::VALUE_MIXED);
1405
-		} else {
1406
-			return $this->getAllValues($app, $key);
1407
-		}
1408
-	}
1409
-
1410
-	/**
1411
-	 * get all values of the app or and filters out sensitive data
1412
-	 *
1413
-	 * @param string $app
1414
-	 *
1415
-	 * @return array
1416
-	 * @deprecated 29.0.0 use {@see getAllValues()}
1417
-	 */
1418
-	public function getFilteredValues($app) {
1419
-		return $this->getAllValues($app, filtered: true);
1420
-	}
1421
-
1422
-
1423
-	/**
1424
-	 * **Warning:** avoid default NULL value for $lazy as this will
1425
-	 * load all lazy values from the database
1426
-	 *
1427
-	 * @param string $app
1428
-	 * @param array<string, string> $values ['key' => 'value']
1429
-	 * @param bool|null $lazy
1430
-	 *
1431
-	 * @return array<string, string|int|float|bool|array>
1432
-	 */
1433
-	private function formatAppValues(string $app, array $values, ?bool $lazy = null): array {
1434
-		foreach ($values as $key => $value) {
1435
-			try {
1436
-				$type = $this->getValueType($app, $key, $lazy);
1437
-			} catch (AppConfigUnknownKeyException) {
1438
-				continue;
1439
-			}
1440
-
1441
-			$values[$key] = $this->convertTypedValue($value, $type);
1442
-		}
1443
-
1444
-		return $values;
1445
-	}
1446
-
1447
-	/**
1448
-	 * convert string value to the expected type
1449
-	 *
1450
-	 * @param string $value
1451
-	 * @param int $type
1452
-	 *
1453
-	 * @return string|int|float|bool|array
1454
-	 */
1455
-	private function convertTypedValue(string $value, int $type): string|int|float|bool|array {
1456
-		switch ($type) {
1457
-			case self::VALUE_INT:
1458
-				return (int)$value;
1459
-			case self::VALUE_FLOAT:
1460
-				return (float)$value;
1461
-			case self::VALUE_BOOL:
1462
-				return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
1463
-			case self::VALUE_ARRAY:
1464
-				try {
1465
-					return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
1466
-				} catch (JsonException $e) {
1467
-					// ignoreable
1468
-				}
1469
-				break;
1470
-		}
1471
-		return $value;
1472
-	}
1473
-
1474
-	/**
1475
-	 * @param string $app
1476
-	 *
1477
-	 * @return string[]
1478
-	 * @deprecated 29.0.0 data sensitivity should be set when calling setValue*()
1479
-	 */
1480
-	private function getSensitiveKeys(string $app): array {
1481
-		$sensitiveValues = [
1482
-			'circles' => [
1483
-				'/^key_pairs$/',
1484
-				'/^local_gskey$/',
1485
-			],
1486
-			'call_summary_bot' => [
1487
-				'/^secret_(.*)$/',
1488
-			],
1489
-			'external' => [
1490
-				'/^sites$/',
1491
-				'/^jwt_token_privkey_(.*)$/',
1492
-			],
1493
-			'globalsiteselector' => [
1494
-				'/^gss\.jwt\.key$/',
1495
-			],
1496
-			'gpgmailer' => [
1497
-				'/^GpgServerKey$/',
1498
-			],
1499
-			'integration_discourse' => [
1500
-				'/^private_key$/',
1501
-				'/^public_key$/',
1502
-			],
1503
-			'integration_dropbox' => [
1504
-				'/^client_id$/',
1505
-				'/^client_secret$/',
1506
-			],
1507
-			'integration_github' => [
1508
-				'/^client_id$/',
1509
-				'/^client_secret$/',
1510
-			],
1511
-			'integration_gitlab' => [
1512
-				'/^client_id$/',
1513
-				'/^client_secret$/',
1514
-				'/^oauth_instance_url$/',
1515
-			],
1516
-			'integration_google' => [
1517
-				'/^client_id$/',
1518
-				'/^client_secret$/',
1519
-			],
1520
-			'integration_jira' => [
1521
-				'/^client_id$/',
1522
-				'/^client_secret$/',
1523
-				'/^forced_instance_url$/',
1524
-			],
1525
-			'integration_onedrive' => [
1526
-				'/^client_id$/',
1527
-				'/^client_secret$/',
1528
-			],
1529
-			'integration_openproject' => [
1530
-				'/^client_id$/',
1531
-				'/^client_secret$/',
1532
-				'/^oauth_instance_url$/',
1533
-			],
1534
-			'integration_reddit' => [
1535
-				'/^client_id$/',
1536
-				'/^client_secret$/',
1537
-			],
1538
-			'integration_suitecrm' => [
1539
-				'/^client_id$/',
1540
-				'/^client_secret$/',
1541
-				'/^oauth_instance_url$/',
1542
-			],
1543
-			'integration_twitter' => [
1544
-				'/^consumer_key$/',
1545
-				'/^consumer_secret$/',
1546
-				'/^followed_user$/',
1547
-			],
1548
-			'integration_zammad' => [
1549
-				'/^client_id$/',
1550
-				'/^client_secret$/',
1551
-				'/^oauth_instance_url$/',
1552
-			],
1553
-			'maps' => [
1554
-				'/^mapboxAPIKEY$/',
1555
-			],
1556
-			'notify_push' => [
1557
-				'/^cookie$/',
1558
-			],
1559
-			'onlyoffice' => [
1560
-				'/^jwt_secret$/',
1561
-			],
1562
-			'passwords' => [
1563
-				'/^SSEv1ServerKey$/',
1564
-			],
1565
-			'serverinfo' => [
1566
-				'/^token$/',
1567
-			],
1568
-			'spreed' => [
1569
-				'/^bridge_bot_password$/',
1570
-				'/^hosted-signaling-server-(.*)$/',
1571
-				'/^recording_servers$/',
1572
-				'/^signaling_servers$/',
1573
-				'/^signaling_ticket_secret$/',
1574
-				'/^signaling_token_privkey_(.*)$/',
1575
-				'/^signaling_token_pubkey_(.*)$/',
1576
-				'/^sip_bridge_dialin_info$/',
1577
-				'/^sip_bridge_shared_secret$/',
1578
-				'/^stun_servers$/',
1579
-				'/^turn_servers$/',
1580
-				'/^turn_server_secret$/',
1581
-			],
1582
-			'support' => [
1583
-				'/^last_response$/',
1584
-				'/^potential_subscription_key$/',
1585
-				'/^subscription_key$/',
1586
-			],
1587
-			'theming' => [
1588
-				'/^imprintUrl$/',
1589
-				'/^privacyUrl$/',
1590
-				'/^slogan$/',
1591
-				'/^url$/',
1592
-			],
1593
-			'twofactor_gateway' => [
1594
-				'/^.*token$/',
1595
-			],
1596
-			'user_ldap' => [
1597
-				'/^(s..)?ldap_agent_password$/',
1598
-			],
1599
-			'user_saml' => [
1600
-				'/^idp-x509cert$/',
1601
-			],
1602
-			'whiteboard' => [
1603
-				'/^jwt_secret_key$/',
1604
-			],
1605
-		];
1606
-
1607
-		return $sensitiveValues[$app] ?? [];
1608
-	}
1609
-
1610
-	/**
1611
-	 * Clear all the cached app config values
1612
-	 * New cache will be generated next time a config value is retrieved
1613
-	 *
1614
-	 * @deprecated 29.0.0 use {@see clearCache()}
1615
-	 */
1616
-	public function clearCachedConfig(): void {
1617
-		$this->clearCache();
1618
-	}
1619
-
1620
-	/**
1621
-	 * Match and apply current use of config values with defined lexicon.
1622
-	 * Set $lazy to NULL only if only interested into checking that $key is alias.
1623
-	 *
1624
-	 * @throws AppConfigUnknownKeyException
1625
-	 * @throws AppConfigTypeConflictException
1626
-	 * @return bool TRUE if everything is fine compared to lexicon or lexicon does not exist
1627
-	 */
1628
-	private function matchAndApplyLexiconDefinition(
1629
-		string $app,
1630
-		string &$key,
1631
-		?bool &$lazy = null,
1632
-		int &$type = self::VALUE_MIXED,
1633
-		?string &$default = null,
1634
-	): bool {
1635
-		if (in_array($key,
1636
-			[
1637
-				'enabled',
1638
-				'installed_version',
1639
-				'types',
1640
-			])) {
1641
-			return true; // we don't break stuff for this list of config keys.
1642
-		}
1643
-		$configDetails = $this->getConfigDetailsFromLexicon($app);
1644
-		if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) {
1645
-			// in case '$rename' is set in ConfigLexiconEntry, we use the new config key
1646
-			$key = $configDetails['aliases'][$key];
1647
-		}
1648
-
1649
-		if (!array_key_exists($key, $configDetails['entries'])) {
1650
-			return $this->applyLexiconStrictness($configDetails['strictness'], 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon');
1651
-		}
1652
-
1653
-		// if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon
1654
-		if ($lazy === null) {
1655
-			return true;
1656
-		}
1657
-
1658
-		/** @var ConfigLexiconEntry $configValue */
1659
-		$configValue = $configDetails['entries'][$key];
1660
-		$type &= ~self::VALUE_SENSITIVE;
1661
-
1662
-		$appConfigValueType = $configValue->getValueType()->toAppConfigFlag();
1663
-		if ($type === self::VALUE_MIXED) {
1664
-			$type = $appConfigValueType; // we overwrite if value was requested as mixed
1665
-		} elseif ($appConfigValueType !== $type) {
1666
-			throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1667
-		}
1668
-
1669
-		$lazy = $configValue->isLazy();
1670
-		// only look for default if needed, default from Lexicon got priority
1671
-		if ($default !== null) {
1672
-			$default = $configValue->getDefault($this->getLexiconPreset()) ?? $default;
1673
-		}
1674
-
1675
-		if ($configValue->isFlagged(self::FLAG_SENSITIVE)) {
1676
-			$type |= self::VALUE_SENSITIVE;
1677
-		}
1678
-		if ($configValue->isDeprecated()) {
1679
-			$this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
1680
-		}
1681
-
1682
-		return true;
1683
-	}
1684
-
1685
-	/**
1686
-	 * manage ConfigLexicon behavior based on strictness set in IConfigLexicon
1687
-	 *
1688
-	 * @param ConfigLexiconStrictness|null $strictness
1689
-	 * @param string $line
1690
-	 *
1691
-	 * @return bool TRUE if conflict can be fully ignored, FALSE if action should be not performed
1692
-	 * @throws AppConfigUnknownKeyException if strictness implies exception
1693
-	 * @see IConfigLexicon::getStrictness()
1694
-	 */
1695
-	private function applyLexiconStrictness(
1696
-		?ConfigLexiconStrictness $strictness,
1697
-		string $line = '',
1698
-	): bool {
1699
-		if ($strictness === null) {
1700
-			return true;
1701
-		}
1702
-
1703
-		switch ($strictness) {
1704
-			case ConfigLexiconStrictness::IGNORE:
1705
-				return true;
1706
-			case ConfigLexiconStrictness::NOTICE:
1707
-				$this->logger->notice($line);
1708
-				return true;
1709
-			case ConfigLexiconStrictness::WARNING:
1710
-				$this->logger->warning($line);
1711
-				return false;
1712
-		}
1713
-
1714
-		throw new AppConfigUnknownKeyException($line);
1715
-	}
1716
-
1717
-	/**
1718
-	 * extract details from registered $appId's config lexicon
1719
-	 *
1720
-	 * @param string $appId
1721
-	 * @internal
1722
-	 *
1723
-	 * @return array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness}
1724
-	 */
1725
-	public function getConfigDetailsFromLexicon(string $appId): array {
1726
-		if (!array_key_exists($appId, $this->configLexiconDetails)) {
1727
-			$entries = $aliases = [];
1728
-			$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
1729
-			$configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
1730
-			foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
1731
-				$entries[$configEntry->getKey()] = $configEntry;
1732
-				if ($configEntry->getRename() !== null) {
1733
-					$aliases[$configEntry->getRename()] = $configEntry->getKey();
1734
-				}
1735
-			}
1736
-
1737
-			$this->configLexiconDetails[$appId] = [
1738
-				'entries' => $entries,
1739
-				'aliases' => $aliases,
1740
-				'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE
1741
-			];
1742
-		}
1743
-
1744
-		return $this->configLexiconDetails[$appId];
1745
-	}
1746
-
1747
-	private function getLexiconEntry(string $appId, string $key): ?ConfigLexiconEntry {
1748
-		return $this->getConfigDetailsFromLexicon($appId)['entries'][$key] ?? null;
1749
-	}
1750
-
1751
-	/**
1752
-	 * if set to TRUE, ignore aliases defined in Config Lexicon during the use of the methods of this class
1753
-	 *
1754
-	 * @internal
1755
-	 */
1756
-	public function ignoreLexiconAliases(bool $ignore): void {
1757
-		$this->ignoreLexiconAliases = $ignore;
1758
-	}
1759
-
1760
-	private function getLexiconPreset(): Preset {
1761
-		if ($this->configLexiconPreset === null) {
1762
-			$this->configLexiconPreset = Preset::tryFrom($this->config->getSystemValueInt(ConfigManager::PRESET_CONFIGKEY, 0)) ?? Preset::NONE;
1763
-		}
1764
-
1765
-		return $this->configLexiconPreset;
1766
-	}
1767
-
1768
-	/**
1769
-	 * Returns the installed versions of all apps
1770
-	 *
1771
-	 * @return array<string, string>
1772
-	 */
1773
-	public function getAppInstalledVersions(bool $onlyEnabled = false): array {
1774
-		if ($this->appVersionsCache === null) {
1775
-			/** @var array<string, string> */
1776
-			$this->appVersionsCache = $this->searchValues('installed_version', false, IAppConfig::VALUE_STRING);
1777
-		}
1778
-		if ($onlyEnabled) {
1779
-			return array_filter(
1780
-				$this->appVersionsCache,
1781
-				fn (string $app): bool => $this->getValueString($app, 'enabled', 'no') !== 'no',
1782
-				ARRAY_FILTER_USE_KEY
1783
-			);
1784
-		}
1785
-		return $this->appVersionsCache;
1786
-	}
52
+    private const APP_MAX_LENGTH = 32;
53
+    private const KEY_MAX_LENGTH = 64;
54
+    private const ENCRYPTION_PREFIX = '$AppConfigEncryption$';
55
+    private const ENCRYPTION_PREFIX_LENGTH = 21; // strlen(self::ENCRYPTION_PREFIX)
56
+
57
+    /** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
58
+    private array $fastCache = [];   // cache for normal config keys
59
+    /** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
60
+    private array $lazyCache = [];   // cache for lazy config keys
61
+    /** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
62
+    private array $valueTypes = [];  // type for all config values
63
+    private bool $fastLoaded = false;
64
+    private bool $lazyLoaded = false;
65
+    /** @var array<string, array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
66
+    private array $configLexiconDetails = [];
67
+    private bool $ignoreLexiconAliases = false;
68
+    private ?Preset $configLexiconPreset = null;
69
+    /** @var ?array<string, string> */
70
+    private ?array $appVersionsCache = null;
71
+
72
+    public function __construct(
73
+        protected IDBConnection $connection,
74
+        protected IConfig $config,
75
+        protected LoggerInterface $logger,
76
+        protected ICrypto $crypto,
77
+    ) {
78
+    }
79
+
80
+    /**
81
+     * @inheritDoc
82
+     *
83
+     * @return list<string> list of app ids
84
+     * @since 7.0.0
85
+     */
86
+    public function getApps(): array {
87
+        $this->loadConfigAll();
88
+        $apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
89
+        sort($apps);
90
+
91
+        return array_values(array_unique($apps));
92
+    }
93
+
94
+    /**
95
+     * @inheritDoc
96
+     *
97
+     * @param string $app id of the app
98
+     * @return list<string> list of stored config keys
99
+     * @see searchKeys to not load lazy config keys
100
+     *
101
+     * @since 29.0.0
102
+     */
103
+    public function getKeys(string $app): array {
104
+        $this->assertParams($app);
105
+        $this->loadConfigAll($app);
106
+        $keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
107
+        sort($keys);
108
+
109
+        return array_values(array_unique($keys));
110
+    }
111
+
112
+    /**
113
+     * @inheritDoc
114
+     *
115
+     * @param string $app id of the app
116
+     * @param string $prefix returns only keys starting with this value
117
+     * @param bool $lazy TRUE to search in lazy config keys
118
+     * @return list<string> list of stored config keys
119
+     * @since 32.0.0
120
+     */
121
+    public function searchKeys(string $app, string $prefix = '', bool $lazy = false): array {
122
+        $this->assertParams($app);
123
+        $this->loadConfig($app, $lazy);
124
+        if ($lazy) {
125
+            $keys = array_keys($this->lazyCache[$app] ?? []);
126
+        } else {
127
+            $keys = array_keys($this->fastCache[$app] ?? []);
128
+        }
129
+
130
+        if ($prefix !== '') {
131
+            $keys = array_filter($keys, static fn (string $key): bool => str_starts_with($key, $prefix));
132
+        }
133
+
134
+        sort($keys);
135
+        return array_values(array_unique($keys));
136
+    }
137
+
138
+    /**
139
+     * @inheritDoc
140
+     *
141
+     * @param string $app id of the app
142
+     * @param string $key config key
143
+     * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
144
+     *
145
+     * @return bool TRUE if key exists
146
+     * @since 7.0.0
147
+     * @since 29.0.0 Added the $lazy argument
148
+     */
149
+    public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
150
+        $this->assertParams($app, $key);
151
+        $this->loadConfig($app, $lazy);
152
+        $this->matchAndApplyLexiconDefinition($app, $key);
153
+
154
+        if ($lazy === null) {
155
+            $appCache = $this->getAllValues($app);
156
+            return isset($appCache[$key]);
157
+        }
158
+
159
+        if ($lazy) {
160
+            return isset($this->lazyCache[$app][$key]);
161
+        }
162
+
163
+        return isset($this->fastCache[$app][$key]);
164
+    }
165
+
166
+    /**
167
+     * @param string $app id of the app
168
+     * @param string $key config key
169
+     * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
170
+     *
171
+     * @return bool
172
+     * @throws AppConfigUnknownKeyException if config key is not known
173
+     * @since 29.0.0
174
+     */
175
+    public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
176
+        $this->assertParams($app, $key);
177
+        $this->loadConfig(null, $lazy);
178
+        $this->matchAndApplyLexiconDefinition($app, $key);
179
+
180
+        if (!isset($this->valueTypes[$app][$key])) {
181
+            throw new AppConfigUnknownKeyException('unknown config key');
182
+        }
183
+
184
+        return $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key]);
185
+    }
186
+
187
+    /**
188
+     * @inheritDoc
189
+     *
190
+     * @param string $app if of the app
191
+     * @param string $key config key
192
+     *
193
+     * @return bool TRUE if config is lazy loaded
194
+     * @throws AppConfigUnknownKeyException if config key is not known
195
+     * @see IAppConfig for details about lazy loading
196
+     * @since 29.0.0
197
+     */
198
+    public function isLazy(string $app, string $key): bool {
199
+        $this->assertParams($app, $key);
200
+        $this->matchAndApplyLexiconDefinition($app, $key);
201
+
202
+        // there is a huge probability the non-lazy config are already loaded
203
+        if ($this->hasKey($app, $key, false)) {
204
+            return false;
205
+        }
206
+
207
+        // key not found, we search in the lazy config
208
+        if ($this->hasKey($app, $key, true)) {
209
+            return true;
210
+        }
211
+
212
+        throw new AppConfigUnknownKeyException('unknown config key');
213
+    }
214
+
215
+
216
+    /**
217
+     * @inheritDoc
218
+     *
219
+     * @param string $app id of the app
220
+     * @param string $prefix config keys prefix to search
221
+     * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
222
+     *
223
+     * @return array<string, string|int|float|bool|array> [configKey => configValue]
224
+     * @since 29.0.0
225
+     */
226
+    public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
227
+        $this->assertParams($app, $prefix);
228
+        // if we want to filter values, we need to get sensitivity
229
+        $this->loadConfigAll($app);
230
+        // array_merge() will remove numeric keys (here config keys), so addition arrays instead
231
+        $values = $this->formatAppValues($app, ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []));
232
+        $values = array_filter(
233
+            $values,
234
+            function (string $key) use ($prefix): bool {
235
+                return str_starts_with($key, $prefix); // filter values based on $prefix
236
+            }, ARRAY_FILTER_USE_KEY
237
+        );
238
+
239
+        if (!$filtered) {
240
+            return $values;
241
+        }
242
+
243
+        /**
244
+         * Using the old (deprecated) list of sensitive values.
245
+         */
246
+        foreach ($this->getSensitiveKeys($app) as $sensitiveKeyExp) {
247
+            $sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
248
+            foreach ($sensitiveKeys as $sensitiveKey) {
249
+                $this->valueTypes[$app][$sensitiveKey] = ($this->valueTypes[$app][$sensitiveKey] ?? 0) | self::VALUE_SENSITIVE;
250
+            }
251
+        }
252
+
253
+        $result = [];
254
+        foreach ($values as $key => $value) {
255
+            $result[$key] = $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key] ?? 0) ? IConfig::SENSITIVE_VALUE : $value;
256
+        }
257
+
258
+        return $result;
259
+    }
260
+
261
+    /**
262
+     * @inheritDoc
263
+     *
264
+     * @param string $key config key
265
+     * @param bool $lazy search within lazy loaded config
266
+     * @param int|null $typedAs enforce type for the returned values ({@see self::VALUE_STRING} and others)
267
+     *
268
+     * @return array<string, string|int|float|bool|array> [appId => configValue]
269
+     * @since 29.0.0
270
+     */
271
+    public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array {
272
+        $this->assertParams('', $key, true);
273
+        $this->loadConfig(null, $lazy);
274
+
275
+        /** @var array<array-key, array<array-key, mixed>> $cache */
276
+        if ($lazy) {
277
+            $cache = $this->lazyCache;
278
+        } else {
279
+            $cache = $this->fastCache;
280
+        }
281
+
282
+        $values = [];
283
+        foreach (array_keys($cache) as $app) {
284
+            if (isset($cache[$app][$key])) {
285
+                $values[$app] = $this->convertTypedValue($cache[$app][$key], $typedAs ?? $this->getValueType((string)$app, $key, $lazy));
286
+            }
287
+        }
288
+
289
+        return $values;
290
+    }
291
+
292
+
293
+    /**
294
+     * Get the config value as string.
295
+     * If the value does not exist the given default will be returned.
296
+     *
297
+     * Set lazy to `null` to ignore it and get the value from either source.
298
+     *
299
+     * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
300
+     *
301
+     * @param string $app id of the app
302
+     * @param string $key config key
303
+     * @param string $default config value
304
+     * @param null|bool $lazy get config as lazy loaded or not. can be NULL
305
+     *
306
+     * @return string the value or $default
307
+     * @internal
308
+     * @since 29.0.0
309
+     * @see IAppConfig for explanation about lazy loading
310
+     * @see getValueString()
311
+     * @see getValueInt()
312
+     * @see getValueFloat()
313
+     * @see getValueBool()
314
+     * @see getValueArray()
315
+     */
316
+    public function getValueMixed(
317
+        string $app,
318
+        string $key,
319
+        string $default = '',
320
+        ?bool $lazy = false,
321
+    ): string {
322
+        try {
323
+            $lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
324
+        } catch (AppConfigUnknownKeyException) {
325
+            return $default;
326
+        }
327
+
328
+        return $this->getTypedValue(
329
+            $app,
330
+            $key,
331
+            $default,
332
+            $lazy,
333
+            self::VALUE_MIXED
334
+        );
335
+    }
336
+
337
+    /**
338
+     * @inheritDoc
339
+     *
340
+     * @param string $app id of the app
341
+     * @param string $key config key
342
+     * @param string $default default value
343
+     * @param bool $lazy search within lazy loaded config
344
+     *
345
+     * @return string stored config value or $default if not set in database
346
+     * @throws InvalidArgumentException if one of the argument format is invalid
347
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
348
+     * @since 29.0.0
349
+     * @see IAppConfig for explanation about lazy loading
350
+     */
351
+    public function getValueString(
352
+        string $app,
353
+        string $key,
354
+        string $default = '',
355
+        bool $lazy = false,
356
+    ): string {
357
+        return $this->getTypedValue($app, $key, $default, $lazy, self::VALUE_STRING);
358
+    }
359
+
360
+    /**
361
+     * @inheritDoc
362
+     *
363
+     * @param string $app id of the app
364
+     * @param string $key config key
365
+     * @param int $default default value
366
+     * @param bool $lazy search within lazy loaded config
367
+     *
368
+     * @return int stored config value or $default if not set in database
369
+     * @throws InvalidArgumentException if one of the argument format is invalid
370
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
371
+     * @since 29.0.0
372
+     * @see IAppConfig for explanation about lazy loading
373
+     */
374
+    public function getValueInt(
375
+        string $app,
376
+        string $key,
377
+        int $default = 0,
378
+        bool $lazy = false,
379
+    ): int {
380
+        return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
381
+    }
382
+
383
+    /**
384
+     * @inheritDoc
385
+     *
386
+     * @param string $app id of the app
387
+     * @param string $key config key
388
+     * @param float $default default value
389
+     * @param bool $lazy search within lazy loaded config
390
+     *
391
+     * @return float stored config value or $default if not set in database
392
+     * @throws InvalidArgumentException if one of the argument format is invalid
393
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
394
+     * @since 29.0.0
395
+     * @see IAppConfig for explanation about lazy loading
396
+     */
397
+    public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
398
+        return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
399
+    }
400
+
401
+    /**
402
+     * @inheritDoc
403
+     *
404
+     * @param string $app id of the app
405
+     * @param string $key config key
406
+     * @param bool $default default value
407
+     * @param bool $lazy search within lazy loaded config
408
+     *
409
+     * @return bool stored config value or $default if not set in database
410
+     * @throws InvalidArgumentException if one of the argument format is invalid
411
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
412
+     * @since 29.0.0
413
+     * @see IAppConfig for explanation about lazy loading
414
+     */
415
+    public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
416
+        $b = strtolower($this->getTypedValue($app, $key, $default ? 'true' : 'false', $lazy, self::VALUE_BOOL));
417
+        return in_array($b, ['1', 'true', 'yes', 'on']);
418
+    }
419
+
420
+    /**
421
+     * @inheritDoc
422
+     *
423
+     * @param string $app id of the app
424
+     * @param string $key config key
425
+     * @param array $default default value
426
+     * @param bool $lazy search within lazy loaded config
427
+     *
428
+     * @return array stored config value or $default if not set in database
429
+     * @throws InvalidArgumentException if one of the argument format is invalid
430
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
431
+     * @since 29.0.0
432
+     * @see IAppConfig for explanation about lazy loading
433
+     */
434
+    public function getValueArray(
435
+        string $app,
436
+        string $key,
437
+        array $default = [],
438
+        bool $lazy = false,
439
+    ): array {
440
+        try {
441
+            $defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
442
+            $value = json_decode($this->getTypedValue($app, $key, $defaultJson, $lazy, self::VALUE_ARRAY), true, flags: JSON_THROW_ON_ERROR);
443
+
444
+            return is_array($value) ? $value : [];
445
+        } catch (JsonException) {
446
+            return [];
447
+        }
448
+    }
449
+
450
+    /**
451
+     * @param string $app id of the app
452
+     * @param string $key config key
453
+     * @param string $default default value
454
+     * @param bool $lazy search within lazy loaded config
455
+     * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT}{@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
456
+     *
457
+     * @return string
458
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
459
+     * @throws InvalidArgumentException
460
+     */
461
+    private function getTypedValue(
462
+        string $app,
463
+        string $key,
464
+        string $default,
465
+        bool $lazy,
466
+        int $type,
467
+    ): string {
468
+        $this->assertParams($app, $key, valueType: $type);
469
+        $origKey = $key;
470
+        $matched = $this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default);
471
+        if ($default === null) {
472
+            // there is no logical reason for it to be null
473
+            throw new \Exception('default cannot be null');
474
+        }
475
+
476
+        // returns default if strictness of lexicon is set to WARNING (block and report)
477
+        if (!$matched) {
478
+            return $default;
479
+        }
480
+
481
+        $this->loadConfig($app, $lazy);
482
+
483
+        /**
484
+         * We ignore check if mixed type is requested.
485
+         * If type of stored value is set as mixed, we don't filter.
486
+         * If type of stored value is defined, we compare with the one requested.
487
+         */
488
+        $knownType = $this->valueTypes[$app][$key] ?? 0;
489
+        if (!$this->isTyped(self::VALUE_MIXED, $type)
490
+            && $knownType > 0
491
+            && !$this->isTyped(self::VALUE_MIXED, $knownType)
492
+            && !$this->isTyped($type, $knownType)) {
493
+            $this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
494
+            throw new AppConfigTypeConflictException('conflict with value type from database');
495
+        }
496
+
497
+        /**
498
+         * - the pair $app/$key cannot exist in both array,
499
+         * - we should still return an existing non-lazy value even if current method
500
+         *   is called with $lazy is true
501
+         *
502
+         * This way, lazyCache will be empty until the load for lazy config value is requested.
503
+         */
504
+        if (isset($this->lazyCache[$app][$key])) {
505
+            $value = $this->lazyCache[$app][$key];
506
+        } elseif (isset($this->fastCache[$app][$key])) {
507
+            $value = $this->fastCache[$app][$key];
508
+        } else {
509
+            return $default;
510
+        }
511
+
512
+        $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $knownType);
513
+        if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
514
+            // Only decrypt values that are stored encrypted
515
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
516
+        }
517
+
518
+        // in case the key was modified while running matchAndApplyLexiconDefinition() we are
519
+        // interested to check options in case a modification of the value is needed
520
+        // ie inverting value from previous key when using lexicon option RENAME_INVERT_BOOLEAN
521
+        if ($origKey !== $key && $type === self::VALUE_BOOL) {
522
+            $configManager = Server::get(ConfigManager::class);
523
+            $value = ($configManager->convertToBool($value, $this->getLexiconEntry($app, $key))) ? '1' : '0';
524
+        }
525
+
526
+        return $value;
527
+    }
528
+
529
+    /**
530
+     * @inheritDoc
531
+     *
532
+     * @param string $app id of the app
533
+     * @param string $key config key
534
+     *
535
+     * @return int type of the value
536
+     * @throws AppConfigUnknownKeyException if config key is not known
537
+     * @since 29.0.0
538
+     * @see VALUE_STRING
539
+     * @see VALUE_INT
540
+     * @see VALUE_FLOAT
541
+     * @see VALUE_BOOL
542
+     * @see VALUE_ARRAY
543
+     */
544
+    public function getValueType(string $app, string $key, ?bool $lazy = null): int {
545
+        $type = self::VALUE_MIXED;
546
+        $ignorable = $lazy ?? false;
547
+        $this->matchAndApplyLexiconDefinition($app, $key, $ignorable, $type);
548
+        if ($type !== self::VALUE_MIXED) {
549
+            // a modified $type means config key is set in Lexicon
550
+            return $type;
551
+        }
552
+
553
+        $this->assertParams($app, $key);
554
+        $this->loadConfig($app, $lazy);
555
+
556
+        if (!isset($this->valueTypes[$app][$key])) {
557
+            throw new AppConfigUnknownKeyException('unknown config key');
558
+        }
559
+
560
+        $type = $this->valueTypes[$app][$key];
561
+        $type &= ~self::VALUE_SENSITIVE;
562
+        return $type;
563
+    }
564
+
565
+
566
+    /**
567
+     * Store a config key and its value in database as VALUE_MIXED
568
+     *
569
+     * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
570
+     *
571
+     * @param string $app id of the app
572
+     * @param string $key config key
573
+     * @param string $value config value
574
+     * @param bool $lazy set config as lazy loaded
575
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
576
+     *
577
+     * @return bool TRUE if value was different, therefor updated in database
578
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED
579
+     * @internal
580
+     * @since 29.0.0
581
+     * @see IAppConfig for explanation about lazy loading
582
+     * @see setValueString()
583
+     * @see setValueInt()
584
+     * @see setValueFloat()
585
+     * @see setValueBool()
586
+     * @see setValueArray()
587
+     */
588
+    public function setValueMixed(
589
+        string $app,
590
+        string $key,
591
+        string $value,
592
+        bool $lazy = false,
593
+        bool $sensitive = false,
594
+    ): bool {
595
+        return $this->setTypedValue(
596
+            $app,
597
+            $key,
598
+            $value,
599
+            $lazy,
600
+            self::VALUE_MIXED | ($sensitive ? self::VALUE_SENSITIVE : 0)
601
+        );
602
+    }
603
+
604
+
605
+    /**
606
+     * @inheritDoc
607
+     *
608
+     * @param string $app id of the app
609
+     * @param string $key config key
610
+     * @param string $value config value
611
+     * @param bool $lazy set config as lazy loaded
612
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
613
+     *
614
+     * @return bool TRUE if value was different, therefor updated in database
615
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
616
+     * @since 29.0.0
617
+     * @see IAppConfig for explanation about lazy loading
618
+     */
619
+    public function setValueString(
620
+        string $app,
621
+        string $key,
622
+        string $value,
623
+        bool $lazy = false,
624
+        bool $sensitive = false,
625
+    ): bool {
626
+        return $this->setTypedValue(
627
+            $app,
628
+            $key,
629
+            $value,
630
+            $lazy,
631
+            self::VALUE_STRING | ($sensitive ? self::VALUE_SENSITIVE : 0)
632
+        );
633
+    }
634
+
635
+    /**
636
+     * @inheritDoc
637
+     *
638
+     * @param string $app id of the app
639
+     * @param string $key config key
640
+     * @param int $value config value
641
+     * @param bool $lazy set config as lazy loaded
642
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
643
+     *
644
+     * @return bool TRUE if value was different, therefor updated in database
645
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
646
+     * @since 29.0.0
647
+     * @see IAppConfig for explanation about lazy loading
648
+     */
649
+    public function setValueInt(
650
+        string $app,
651
+        string $key,
652
+        int $value,
653
+        bool $lazy = false,
654
+        bool $sensitive = false,
655
+    ): bool {
656
+        if ($value > 2000000000) {
657
+            $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.');
658
+        }
659
+
660
+        return $this->setTypedValue(
661
+            $app,
662
+            $key,
663
+            (string)$value,
664
+            $lazy,
665
+            self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
666
+        );
667
+    }
668
+
669
+    /**
670
+     * @inheritDoc
671
+     *
672
+     * @param string $app id of the app
673
+     * @param string $key config key
674
+     * @param float $value config value
675
+     * @param bool $lazy set config as lazy loaded
676
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
677
+     *
678
+     * @return bool TRUE if value was different, therefor updated in database
679
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
680
+     * @since 29.0.0
681
+     * @see IAppConfig for explanation about lazy loading
682
+     */
683
+    public function setValueFloat(
684
+        string $app,
685
+        string $key,
686
+        float $value,
687
+        bool $lazy = false,
688
+        bool $sensitive = false,
689
+    ): bool {
690
+        return $this->setTypedValue(
691
+            $app,
692
+            $key,
693
+            (string)$value,
694
+            $lazy,
695
+            self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
696
+        );
697
+    }
698
+
699
+    /**
700
+     * @inheritDoc
701
+     *
702
+     * @param string $app id of the app
703
+     * @param string $key config key
704
+     * @param bool $value config value
705
+     * @param bool $lazy set config as lazy loaded
706
+     *
707
+     * @return bool TRUE if value was different, therefor updated in database
708
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
709
+     * @since 29.0.0
710
+     * @see IAppConfig for explanation about lazy loading
711
+     */
712
+    public function setValueBool(
713
+        string $app,
714
+        string $key,
715
+        bool $value,
716
+        bool $lazy = false,
717
+    ): bool {
718
+        return $this->setTypedValue(
719
+            $app,
720
+            $key,
721
+            ($value) ? '1' : '0',
722
+            $lazy,
723
+            self::VALUE_BOOL
724
+        );
725
+    }
726
+
727
+    /**
728
+     * @inheritDoc
729
+     *
730
+     * @param string $app id of the app
731
+     * @param string $key config key
732
+     * @param array $value config value
733
+     * @param bool $lazy set config as lazy loaded
734
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
735
+     *
736
+     * @return bool TRUE if value was different, therefor updated in database
737
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
738
+     * @throws JsonException
739
+     * @since 29.0.0
740
+     * @see IAppConfig for explanation about lazy loading
741
+     */
742
+    public function setValueArray(
743
+        string $app,
744
+        string $key,
745
+        array $value,
746
+        bool $lazy = false,
747
+        bool $sensitive = false,
748
+    ): bool {
749
+        try {
750
+            return $this->setTypedValue(
751
+                $app,
752
+                $key,
753
+                json_encode($value, JSON_THROW_ON_ERROR),
754
+                $lazy,
755
+                self::VALUE_ARRAY | ($sensitive ? self::VALUE_SENSITIVE : 0)
756
+            );
757
+        } catch (JsonException $e) {
758
+            $this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
759
+            throw $e;
760
+        }
761
+    }
762
+
763
+    /**
764
+     * Store a config key and its value in database
765
+     *
766
+     * If config key is already known with the exact same config value and same sensitive/lazy status, the
767
+     * database is not updated. If config value was previously stored as sensitive, status will not be
768
+     * altered.
769
+     *
770
+     * @param string $app id of the app
771
+     * @param string $key config key
772
+     * @param string $value config value
773
+     * @param bool $lazy config set as lazy loaded
774
+     * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
775
+     *
776
+     * @return bool TRUE if value was updated in database
777
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
778
+     * @see IAppConfig for explanation about lazy loading
779
+     */
780
+    private function setTypedValue(
781
+        string $app,
782
+        string $key,
783
+        string $value,
784
+        bool $lazy,
785
+        int $type,
786
+    ): bool {
787
+        $this->assertParams($app, $key);
788
+        if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
789
+            return false; // returns false as database is not updated
790
+        }
791
+        $this->loadConfig(null, $lazy);
792
+
793
+        $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
794
+        $inserted = $refreshCache = false;
795
+
796
+        $origValue = $value;
797
+        if ($sensitive || ($this->hasKey($app, $key, $lazy) && $this->isSensitive($app, $key, $lazy))) {
798
+            $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
799
+        }
800
+
801
+        if ($this->hasKey($app, $key, $lazy)) {
802
+            /**
803
+             * no update if key is already known with set lazy status and value is
804
+             * not different, unless sensitivity is switched from false to true.
805
+             */
806
+            if ($origValue === $this->getTypedValue($app, $key, $value, $lazy, $type)
807
+                && (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
808
+                return false;
809
+            }
810
+        } else {
811
+            /**
812
+             * if key is not known yet, we try to insert.
813
+             * It might fail if the key exists with a different lazy flag.
814
+             */
815
+            try {
816
+                $insert = $this->connection->getQueryBuilder();
817
+                $insert->insert('appconfig')
818
+                    ->setValue('appid', $insert->createNamedParameter($app))
819
+                    ->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
820
+                    ->setValue('type', $insert->createNamedParameter($type, IQueryBuilder::PARAM_INT))
821
+                    ->setValue('configkey', $insert->createNamedParameter($key))
822
+                    ->setValue('configvalue', $insert->createNamedParameter($value));
823
+                $insert->executeStatement();
824
+                $inserted = true;
825
+            } catch (DBException $e) {
826
+                if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
827
+                    throw $e; // TODO: throw exception or just log and returns false !?
828
+                }
829
+            }
830
+        }
831
+
832
+        /**
833
+         * We cannot insert a new row, meaning we need to update an already existing one
834
+         */
835
+        if (!$inserted) {
836
+            $currType = $this->valueTypes[$app][$key] ?? 0;
837
+            if ($currType === 0) { // this might happen when switching lazy loading status
838
+                $this->loadConfigAll();
839
+                $currType = $this->valueTypes[$app][$key] ?? 0;
840
+            }
841
+
842
+            /**
843
+             * This should only happen during the upgrade process from 28 to 29.
844
+             * We only log a warning and set it to VALUE_MIXED.
845
+             */
846
+            if ($currType === 0) {
847
+                $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]);
848
+                $currType = self::VALUE_MIXED;
849
+            }
850
+
851
+            /**
852
+             * we only accept a different type from the one stored in database
853
+             * if the one stored in database is not-defined (VALUE_MIXED)
854
+             */
855
+            if (!$this->isTyped(self::VALUE_MIXED, $currType)
856
+                && ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
857
+                try {
858
+                    $currType = $this->convertTypeToString($currType);
859
+                    $type = $this->convertTypeToString($type);
860
+                } catch (AppConfigIncorrectTypeException) {
861
+                    // can be ignored, this was just needed for a better exception message.
862
+                }
863
+                throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
864
+            }
865
+
866
+            // we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
867
+            if ($sensitive || $this->isTyped(self::VALUE_SENSITIVE, $currType)) {
868
+                $type |= self::VALUE_SENSITIVE;
869
+            }
870
+
871
+            if ($lazy !== $this->isLazy($app, $key)) {
872
+                $refreshCache = true;
873
+            }
874
+
875
+            $update = $this->connection->getQueryBuilder();
876
+            $update->update('appconfig')
877
+                ->set('configvalue', $update->createNamedParameter($value))
878
+                ->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
879
+                ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
880
+                ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
881
+                ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
882
+
883
+            $update->executeStatement();
884
+        }
885
+
886
+        if ($refreshCache) {
887
+            $this->clearCache();
888
+            return true;
889
+        }
890
+
891
+        // update local cache
892
+        if ($lazy) {
893
+            $this->lazyCache[$app][$key] = $value;
894
+        } else {
895
+            $this->fastCache[$app][$key] = $value;
896
+        }
897
+        $this->valueTypes[$app][$key] = $type;
898
+
899
+        return true;
900
+    }
901
+
902
+    /**
903
+     * Change the type of config value.
904
+     *
905
+     * **WARNING:** Method is internal and **MUST** not be used as it may break things.
906
+     *
907
+     * @param string $app id of the app
908
+     * @param string $key config key
909
+     * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
910
+     *
911
+     * @return bool TRUE if database update were necessary
912
+     * @throws AppConfigUnknownKeyException if $key is now known in database
913
+     * @throws AppConfigIncorrectTypeException if $type is not valid
914
+     * @internal
915
+     * @since 29.0.0
916
+     */
917
+    public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
918
+        $this->assertParams($app, $key);
919
+        $this->loadConfigAll();
920
+        $this->matchAndApplyLexiconDefinition($app, $key);
921
+        $this->isLazy($app, $key); // confirm key exists
922
+
923
+        // type can only be one type
924
+        if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
925
+            throw new AppConfigIncorrectTypeException('Unknown value type');
926
+        }
927
+
928
+        $currType = $this->valueTypes[$app][$key];
929
+        if (($type | self::VALUE_SENSITIVE) === ($currType | self::VALUE_SENSITIVE)) {
930
+            return false;
931
+        }
932
+
933
+        // we complete with sensitive flag if the stored value is set as sensitive
934
+        if ($this->isTyped(self::VALUE_SENSITIVE, $currType)) {
935
+            $type = $type | self::VALUE_SENSITIVE;
936
+        }
937
+
938
+        $update = $this->connection->getQueryBuilder();
939
+        $update->update('appconfig')
940
+            ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
941
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
942
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
943
+        $update->executeStatement();
944
+        $this->valueTypes[$app][$key] = $type;
945
+
946
+        return true;
947
+    }
948
+
949
+
950
+    /**
951
+     * @inheritDoc
952
+     *
953
+     * @param string $app id of the app
954
+     * @param string $key config key
955
+     * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
956
+     *
957
+     * @return bool TRUE if entry was found in database and an update was necessary
958
+     * @since 29.0.0
959
+     */
960
+    public function updateSensitive(string $app, string $key, bool $sensitive): bool {
961
+        $this->assertParams($app, $key);
962
+        $this->loadConfigAll();
963
+        $this->matchAndApplyLexiconDefinition($app, $key);
964
+
965
+        try {
966
+            if ($sensitive === $this->isSensitive($app, $key, null)) {
967
+                return false;
968
+            }
969
+        } catch (AppConfigUnknownKeyException $e) {
970
+            return false;
971
+        }
972
+
973
+        $lazy = $this->isLazy($app, $key);
974
+        if ($lazy) {
975
+            $cache = $this->lazyCache;
976
+        } else {
977
+            $cache = $this->fastCache;
978
+        }
979
+
980
+        if (!isset($cache[$app][$key])) {
981
+            throw new AppConfigUnknownKeyException('unknown config key');
982
+        }
983
+
984
+        /**
985
+         * type returned by getValueType() is already cleaned from sensitive flag
986
+         * we just need to update it based on $sensitive and store it in database
987
+         */
988
+        $type = $this->getValueType($app, $key);
989
+        $value = $cache[$app][$key];
990
+        if ($sensitive) {
991
+            $type |= self::VALUE_SENSITIVE;
992
+            $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
993
+        } else {
994
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
995
+        }
996
+
997
+        $update = $this->connection->getQueryBuilder();
998
+        $update->update('appconfig')
999
+            ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
1000
+            ->set('configvalue', $update->createNamedParameter($value))
1001
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1002
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1003
+        $update->executeStatement();
1004
+
1005
+        $this->valueTypes[$app][$key] = $type;
1006
+
1007
+        return true;
1008
+    }
1009
+
1010
+    /**
1011
+     * @inheritDoc
1012
+     *
1013
+     * @param string $app id of the app
1014
+     * @param string $key config key
1015
+     * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
1016
+     *
1017
+     * @return bool TRUE if entry was found in database and an update was necessary
1018
+     * @since 29.0.0
1019
+     */
1020
+    public function updateLazy(string $app, string $key, bool $lazy): bool {
1021
+        $this->assertParams($app, $key);
1022
+        $this->loadConfigAll();
1023
+        $this->matchAndApplyLexiconDefinition($app, $key);
1024
+
1025
+        try {
1026
+            if ($lazy === $this->isLazy($app, $key)) {
1027
+                return false;
1028
+            }
1029
+        } catch (AppConfigUnknownKeyException $e) {
1030
+            return false;
1031
+        }
1032
+
1033
+        $update = $this->connection->getQueryBuilder();
1034
+        $update->update('appconfig')
1035
+            ->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
1036
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
1037
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
1038
+        $update->executeStatement();
1039
+
1040
+        // At this point, it is a lot safer to clean cache
1041
+        $this->clearCache();
1042
+
1043
+        return true;
1044
+    }
1045
+
1046
+    /**
1047
+     * @inheritDoc
1048
+     *
1049
+     * @param string $app id of the app
1050
+     * @param string $key config key
1051
+     *
1052
+     * @return array
1053
+     * @throws AppConfigUnknownKeyException if config key is not known in database
1054
+     * @since 29.0.0
1055
+     */
1056
+    public function getDetails(string $app, string $key): array {
1057
+        $this->assertParams($app, $key);
1058
+        $this->loadConfigAll();
1059
+        $this->matchAndApplyLexiconDefinition($app, $key);
1060
+        $lazy = $this->isLazy($app, $key);
1061
+
1062
+        if ($lazy) {
1063
+            $cache = $this->lazyCache;
1064
+        } else {
1065
+            $cache = $this->fastCache;
1066
+        }
1067
+
1068
+        $type = $this->getValueType($app, $key);
1069
+        try {
1070
+            $typeString = $this->convertTypeToString($type);
1071
+        } catch (AppConfigIncorrectTypeException $e) {
1072
+            $this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
1073
+            $typeString = (string)$type;
1074
+        }
1075
+
1076
+        if (!isset($cache[$app][$key])) {
1077
+            throw new AppConfigUnknownKeyException('unknown config key');
1078
+        }
1079
+
1080
+        $value = $cache[$app][$key];
1081
+        $sensitive = $this->isSensitive($app, $key, null);
1082
+        if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
1083
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1084
+        }
1085
+
1086
+        return [
1087
+            'app' => $app,
1088
+            'key' => $key,
1089
+            'value' => $value,
1090
+            'type' => $type,
1091
+            'lazy' => $lazy,
1092
+            'typeString' => $typeString,
1093
+            'sensitive' => $sensitive
1094
+        ];
1095
+    }
1096
+
1097
+    /**
1098
+     * @param string $type
1099
+     *
1100
+     * @return int
1101
+     * @throws AppConfigIncorrectTypeException
1102
+     * @since 29.0.0
1103
+     */
1104
+    public function convertTypeToInt(string $type): int {
1105
+        return match (strtolower($type)) {
1106
+            'mixed' => IAppConfig::VALUE_MIXED,
1107
+            'string' => IAppConfig::VALUE_STRING,
1108
+            'integer' => IAppConfig::VALUE_INT,
1109
+            'float' => IAppConfig::VALUE_FLOAT,
1110
+            'boolean' => IAppConfig::VALUE_BOOL,
1111
+            'array' => IAppConfig::VALUE_ARRAY,
1112
+            default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
1113
+        };
1114
+    }
1115
+
1116
+    /**
1117
+     * @param int $type
1118
+     *
1119
+     * @return string
1120
+     * @throws AppConfigIncorrectTypeException
1121
+     * @since 29.0.0
1122
+     */
1123
+    public function convertTypeToString(int $type): string {
1124
+        $type &= ~self::VALUE_SENSITIVE;
1125
+
1126
+        return match ($type) {
1127
+            IAppConfig::VALUE_MIXED => 'mixed',
1128
+            IAppConfig::VALUE_STRING => 'string',
1129
+            IAppConfig::VALUE_INT => 'integer',
1130
+            IAppConfig::VALUE_FLOAT => 'float',
1131
+            IAppConfig::VALUE_BOOL => 'boolean',
1132
+            IAppConfig::VALUE_ARRAY => 'array',
1133
+            default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
1134
+        };
1135
+    }
1136
+
1137
+    /**
1138
+     * @inheritDoc
1139
+     *
1140
+     * @param string $app id of the app
1141
+     * @param string $key config key
1142
+     *
1143
+     * @since 29.0.0
1144
+     */
1145
+    public function deleteKey(string $app, string $key): void {
1146
+        $this->assertParams($app, $key);
1147
+        $this->matchAndApplyLexiconDefinition($app, $key);
1148
+
1149
+        $qb = $this->connection->getQueryBuilder();
1150
+        $qb->delete('appconfig')
1151
+            ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1152
+            ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1153
+        $qb->executeStatement();
1154
+
1155
+        unset($this->lazyCache[$app][$key]);
1156
+        unset($this->fastCache[$app][$key]);
1157
+        unset($this->valueTypes[$app][$key]);
1158
+    }
1159
+
1160
+    /**
1161
+     * @inheritDoc
1162
+     *
1163
+     * @param string $app id of the app
1164
+     *
1165
+     * @since 29.0.0
1166
+     */
1167
+    public function deleteApp(string $app): void {
1168
+        $this->assertParams($app);
1169
+        $qb = $this->connection->getQueryBuilder();
1170
+        $qb->delete('appconfig')
1171
+            ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
1172
+        $qb->executeStatement();
1173
+
1174
+        $this->clearCache();
1175
+    }
1176
+
1177
+    /**
1178
+     * @inheritDoc
1179
+     *
1180
+     * @param bool $reload set to TRUE to refill cache instantly after clearing it
1181
+     *
1182
+     * @since 29.0.0
1183
+     */
1184
+    public function clearCache(bool $reload = false): void {
1185
+        $this->lazyLoaded = $this->fastLoaded = false;
1186
+        $this->lazyCache = $this->fastCache = $this->valueTypes = $this->configLexiconDetails = [];
1187
+        $this->configLexiconPreset = null;
1188
+
1189
+        if (!$reload) {
1190
+            return;
1191
+        }
1192
+
1193
+        $this->loadConfigAll();
1194
+    }
1195
+
1196
+
1197
+    /**
1198
+     * For debug purpose.
1199
+     * Returns the cached data.
1200
+     *
1201
+     * @return array
1202
+     * @since 29.0.0
1203
+     * @internal
1204
+     */
1205
+    public function statusCache(): array {
1206
+        return [
1207
+            'fastLoaded' => $this->fastLoaded,
1208
+            'fastCache' => $this->fastCache,
1209
+            'lazyLoaded' => $this->lazyLoaded,
1210
+            'lazyCache' => $this->lazyCache,
1211
+        ];
1212
+    }
1213
+
1214
+    /**
1215
+     * @param int $needle bitflag to search
1216
+     * @param int $type known value
1217
+     *
1218
+     * @return bool TRUE if bitflag $needle is set in $type
1219
+     */
1220
+    private function isTyped(int $needle, int $type): bool {
1221
+        return (($needle & $type) !== 0);
1222
+    }
1223
+
1224
+    /**
1225
+     * Confirm the string set for app and key fit the database description
1226
+     *
1227
+     * @param string $app assert $app fit in database
1228
+     * @param string $configKey assert config key fit in database
1229
+     * @param bool $allowEmptyApp $app can be empty string
1230
+     * @param int $valueType assert value type is only one type
1231
+     *
1232
+     * @throws InvalidArgumentException
1233
+     */
1234
+    private function assertParams(string $app = '', string $configKey = '', bool $allowEmptyApp = false, int $valueType = -1): void {
1235
+        if (!$allowEmptyApp && $app === '') {
1236
+            throw new InvalidArgumentException('app cannot be an empty string');
1237
+        }
1238
+        if (strlen($app) > self::APP_MAX_LENGTH) {
1239
+            throw new InvalidArgumentException(
1240
+                'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
1241
+            );
1242
+        }
1243
+        if (strlen($configKey) > self::KEY_MAX_LENGTH) {
1244
+            throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
1245
+        }
1246
+        if ($valueType > -1) {
1247
+            $valueType &= ~self::VALUE_SENSITIVE;
1248
+            if (!in_array($valueType, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
1249
+                throw new InvalidArgumentException('Unknown value type');
1250
+            }
1251
+        }
1252
+    }
1253
+
1254
+    private function loadConfigAll(?string $app = null): void {
1255
+        $this->loadConfig($app, null);
1256
+    }
1257
+
1258
+    /**
1259
+     * Load normal config or config set as lazy loaded
1260
+     *
1261
+     * @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
1262
+     */
1263
+    private function loadConfig(?string $app = null, ?bool $lazy = false): void {
1264
+        if ($this->isLoaded($lazy)) {
1265
+            return;
1266
+        }
1267
+
1268
+        // if lazy is null or true, we debug log
1269
+        if (($lazy ?? true) !== false && $app !== null) {
1270
+            $exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "' . $app . '"');
1271
+            $this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
1272
+        }
1273
+
1274
+        $qb = $this->connection->getQueryBuilder();
1275
+        $qb->from('appconfig');
1276
+
1277
+        // we only need value from lazy when loadConfig does not specify it
1278
+        $qb->select('appid', 'configkey', 'configvalue', 'type');
1279
+
1280
+        if ($lazy !== null) {
1281
+            $qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
1282
+        } else {
1283
+            $qb->addSelect('lazy');
1284
+        }
1285
+
1286
+        $result = $qb->executeQuery();
1287
+        $rows = $result->fetchAll();
1288
+        foreach ($rows as $row) {
1289
+            // most of the time, 'lazy' is not in the select because its value is already known
1290
+            if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
1291
+                $this->lazyCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1292
+            } else {
1293
+                $this->fastCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1294
+            }
1295
+            $this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
1296
+        }
1297
+        $result->closeCursor();
1298
+        $this->setAsLoaded($lazy);
1299
+    }
1300
+
1301
+    /**
1302
+     * if $lazy is:
1303
+     *  - false: will returns true if fast config is loaded
1304
+     *  - true : will returns true if lazy config is loaded
1305
+     *  - null : will returns true if both config are loaded
1306
+     *
1307
+     * @param bool $lazy
1308
+     *
1309
+     * @return bool
1310
+     */
1311
+    private function isLoaded(?bool $lazy): bool {
1312
+        if ($lazy === null) {
1313
+            return $this->lazyLoaded && $this->fastLoaded;
1314
+        }
1315
+
1316
+        return $lazy ? $this->lazyLoaded : $this->fastLoaded;
1317
+    }
1318
+
1319
+    /**
1320
+     * if $lazy is:
1321
+     * - false: set fast config as loaded
1322
+     * - true : set lazy config as loaded
1323
+     * - null : set both config as loaded
1324
+     *
1325
+     * @param bool $lazy
1326
+     */
1327
+    private function setAsLoaded(?bool $lazy): void {
1328
+        if ($lazy === null) {
1329
+            $this->fastLoaded = true;
1330
+            $this->lazyLoaded = true;
1331
+
1332
+            return;
1333
+        }
1334
+
1335
+        if ($lazy) {
1336
+            $this->lazyLoaded = true;
1337
+        } else {
1338
+            $this->fastLoaded = true;
1339
+        }
1340
+    }
1341
+
1342
+    /**
1343
+     * Gets the config value
1344
+     *
1345
+     * @param string $app app
1346
+     * @param string $key key
1347
+     * @param string $default = null, default value if the key does not exist
1348
+     *
1349
+     * @return string the value or $default
1350
+     * @deprecated 29.0.0 use getValue*()
1351
+     *
1352
+     * This function gets a value from the appconfig table. If the key does
1353
+     * not exist the default value will be returned
1354
+     */
1355
+    public function getValue($app, $key, $default = null) {
1356
+        $this->loadConfig($app);
1357
+        $this->matchAndApplyLexiconDefinition($app, $key);
1358
+
1359
+        return $this->fastCache[$app][$key] ?? $default;
1360
+    }
1361
+
1362
+    /**
1363
+     * Sets a value. If the key did not exist before it will be created.
1364
+     *
1365
+     * @param string $app app
1366
+     * @param string $key key
1367
+     * @param string|float|int $value value
1368
+     *
1369
+     * @return bool True if the value was inserted or updated, false if the value was the same
1370
+     * @throws AppConfigTypeConflictException
1371
+     * @throws AppConfigUnknownKeyException
1372
+     * @deprecated 29.0.0
1373
+     */
1374
+    public function setValue($app, $key, $value) {
1375
+        /**
1376
+         * TODO: would it be overkill, or decently improve performance, to catch
1377
+         * call to this method with $key='enabled' and 'hide' config value related
1378
+         * to $app when the app is disabled (by modifying entry in database: lazy=lazy+2)
1379
+         * or enabled (lazy=lazy-2)
1380
+         *
1381
+         * this solution would remove the loading of config values from disabled app
1382
+         * unless calling the method {@see loadConfigAll()}
1383
+         */
1384
+        return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
1385
+    }
1386
+
1387
+
1388
+    /**
1389
+     * get multiple values, either the app or key can be used as wildcard by setting it to false
1390
+     *
1391
+     * @param string|false $app
1392
+     * @param string|false $key
1393
+     *
1394
+     * @return array|false
1395
+     * @deprecated 29.0.0 use {@see getAllValues()}
1396
+     */
1397
+    public function getValues($app, $key) {
1398
+        if (($app !== false) === ($key !== false)) {
1399
+            return false;
1400
+        }
1401
+
1402
+        $key = ($key === false) ? '' : $key;
1403
+        if (!$app) {
1404
+            return $this->searchValues($key, false, self::VALUE_MIXED);
1405
+        } else {
1406
+            return $this->getAllValues($app, $key);
1407
+        }
1408
+    }
1409
+
1410
+    /**
1411
+     * get all values of the app or and filters out sensitive data
1412
+     *
1413
+     * @param string $app
1414
+     *
1415
+     * @return array
1416
+     * @deprecated 29.0.0 use {@see getAllValues()}
1417
+     */
1418
+    public function getFilteredValues($app) {
1419
+        return $this->getAllValues($app, filtered: true);
1420
+    }
1421
+
1422
+
1423
+    /**
1424
+     * **Warning:** avoid default NULL value for $lazy as this will
1425
+     * load all lazy values from the database
1426
+     *
1427
+     * @param string $app
1428
+     * @param array<string, string> $values ['key' => 'value']
1429
+     * @param bool|null $lazy
1430
+     *
1431
+     * @return array<string, string|int|float|bool|array>
1432
+     */
1433
+    private function formatAppValues(string $app, array $values, ?bool $lazy = null): array {
1434
+        foreach ($values as $key => $value) {
1435
+            try {
1436
+                $type = $this->getValueType($app, $key, $lazy);
1437
+            } catch (AppConfigUnknownKeyException) {
1438
+                continue;
1439
+            }
1440
+
1441
+            $values[$key] = $this->convertTypedValue($value, $type);
1442
+        }
1443
+
1444
+        return $values;
1445
+    }
1446
+
1447
+    /**
1448
+     * convert string value to the expected type
1449
+     *
1450
+     * @param string $value
1451
+     * @param int $type
1452
+     *
1453
+     * @return string|int|float|bool|array
1454
+     */
1455
+    private function convertTypedValue(string $value, int $type): string|int|float|bool|array {
1456
+        switch ($type) {
1457
+            case self::VALUE_INT:
1458
+                return (int)$value;
1459
+            case self::VALUE_FLOAT:
1460
+                return (float)$value;
1461
+            case self::VALUE_BOOL:
1462
+                return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
1463
+            case self::VALUE_ARRAY:
1464
+                try {
1465
+                    return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
1466
+                } catch (JsonException $e) {
1467
+                    // ignoreable
1468
+                }
1469
+                break;
1470
+        }
1471
+        return $value;
1472
+    }
1473
+
1474
+    /**
1475
+     * @param string $app
1476
+     *
1477
+     * @return string[]
1478
+     * @deprecated 29.0.0 data sensitivity should be set when calling setValue*()
1479
+     */
1480
+    private function getSensitiveKeys(string $app): array {
1481
+        $sensitiveValues = [
1482
+            'circles' => [
1483
+                '/^key_pairs$/',
1484
+                '/^local_gskey$/',
1485
+            ],
1486
+            'call_summary_bot' => [
1487
+                '/^secret_(.*)$/',
1488
+            ],
1489
+            'external' => [
1490
+                '/^sites$/',
1491
+                '/^jwt_token_privkey_(.*)$/',
1492
+            ],
1493
+            'globalsiteselector' => [
1494
+                '/^gss\.jwt\.key$/',
1495
+            ],
1496
+            'gpgmailer' => [
1497
+                '/^GpgServerKey$/',
1498
+            ],
1499
+            'integration_discourse' => [
1500
+                '/^private_key$/',
1501
+                '/^public_key$/',
1502
+            ],
1503
+            'integration_dropbox' => [
1504
+                '/^client_id$/',
1505
+                '/^client_secret$/',
1506
+            ],
1507
+            'integration_github' => [
1508
+                '/^client_id$/',
1509
+                '/^client_secret$/',
1510
+            ],
1511
+            'integration_gitlab' => [
1512
+                '/^client_id$/',
1513
+                '/^client_secret$/',
1514
+                '/^oauth_instance_url$/',
1515
+            ],
1516
+            'integration_google' => [
1517
+                '/^client_id$/',
1518
+                '/^client_secret$/',
1519
+            ],
1520
+            'integration_jira' => [
1521
+                '/^client_id$/',
1522
+                '/^client_secret$/',
1523
+                '/^forced_instance_url$/',
1524
+            ],
1525
+            'integration_onedrive' => [
1526
+                '/^client_id$/',
1527
+                '/^client_secret$/',
1528
+            ],
1529
+            'integration_openproject' => [
1530
+                '/^client_id$/',
1531
+                '/^client_secret$/',
1532
+                '/^oauth_instance_url$/',
1533
+            ],
1534
+            'integration_reddit' => [
1535
+                '/^client_id$/',
1536
+                '/^client_secret$/',
1537
+            ],
1538
+            'integration_suitecrm' => [
1539
+                '/^client_id$/',
1540
+                '/^client_secret$/',
1541
+                '/^oauth_instance_url$/',
1542
+            ],
1543
+            'integration_twitter' => [
1544
+                '/^consumer_key$/',
1545
+                '/^consumer_secret$/',
1546
+                '/^followed_user$/',
1547
+            ],
1548
+            'integration_zammad' => [
1549
+                '/^client_id$/',
1550
+                '/^client_secret$/',
1551
+                '/^oauth_instance_url$/',
1552
+            ],
1553
+            'maps' => [
1554
+                '/^mapboxAPIKEY$/',
1555
+            ],
1556
+            'notify_push' => [
1557
+                '/^cookie$/',
1558
+            ],
1559
+            'onlyoffice' => [
1560
+                '/^jwt_secret$/',
1561
+            ],
1562
+            'passwords' => [
1563
+                '/^SSEv1ServerKey$/',
1564
+            ],
1565
+            'serverinfo' => [
1566
+                '/^token$/',
1567
+            ],
1568
+            'spreed' => [
1569
+                '/^bridge_bot_password$/',
1570
+                '/^hosted-signaling-server-(.*)$/',
1571
+                '/^recording_servers$/',
1572
+                '/^signaling_servers$/',
1573
+                '/^signaling_ticket_secret$/',
1574
+                '/^signaling_token_privkey_(.*)$/',
1575
+                '/^signaling_token_pubkey_(.*)$/',
1576
+                '/^sip_bridge_dialin_info$/',
1577
+                '/^sip_bridge_shared_secret$/',
1578
+                '/^stun_servers$/',
1579
+                '/^turn_servers$/',
1580
+                '/^turn_server_secret$/',
1581
+            ],
1582
+            'support' => [
1583
+                '/^last_response$/',
1584
+                '/^potential_subscription_key$/',
1585
+                '/^subscription_key$/',
1586
+            ],
1587
+            'theming' => [
1588
+                '/^imprintUrl$/',
1589
+                '/^privacyUrl$/',
1590
+                '/^slogan$/',
1591
+                '/^url$/',
1592
+            ],
1593
+            'twofactor_gateway' => [
1594
+                '/^.*token$/',
1595
+            ],
1596
+            'user_ldap' => [
1597
+                '/^(s..)?ldap_agent_password$/',
1598
+            ],
1599
+            'user_saml' => [
1600
+                '/^idp-x509cert$/',
1601
+            ],
1602
+            'whiteboard' => [
1603
+                '/^jwt_secret_key$/',
1604
+            ],
1605
+        ];
1606
+
1607
+        return $sensitiveValues[$app] ?? [];
1608
+    }
1609
+
1610
+    /**
1611
+     * Clear all the cached app config values
1612
+     * New cache will be generated next time a config value is retrieved
1613
+     *
1614
+     * @deprecated 29.0.0 use {@see clearCache()}
1615
+     */
1616
+    public function clearCachedConfig(): void {
1617
+        $this->clearCache();
1618
+    }
1619
+
1620
+    /**
1621
+     * Match and apply current use of config values with defined lexicon.
1622
+     * Set $lazy to NULL only if only interested into checking that $key is alias.
1623
+     *
1624
+     * @throws AppConfigUnknownKeyException
1625
+     * @throws AppConfigTypeConflictException
1626
+     * @return bool TRUE if everything is fine compared to lexicon or lexicon does not exist
1627
+     */
1628
+    private function matchAndApplyLexiconDefinition(
1629
+        string $app,
1630
+        string &$key,
1631
+        ?bool &$lazy = null,
1632
+        int &$type = self::VALUE_MIXED,
1633
+        ?string &$default = null,
1634
+    ): bool {
1635
+        if (in_array($key,
1636
+            [
1637
+                'enabled',
1638
+                'installed_version',
1639
+                'types',
1640
+            ])) {
1641
+            return true; // we don't break stuff for this list of config keys.
1642
+        }
1643
+        $configDetails = $this->getConfigDetailsFromLexicon($app);
1644
+        if (array_key_exists($key, $configDetails['aliases']) && !$this->ignoreLexiconAliases) {
1645
+            // in case '$rename' is set in ConfigLexiconEntry, we use the new config key
1646
+            $key = $configDetails['aliases'][$key];
1647
+        }
1648
+
1649
+        if (!array_key_exists($key, $configDetails['entries'])) {
1650
+            return $this->applyLexiconStrictness($configDetails['strictness'], 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon');
1651
+        }
1652
+
1653
+        // if lazy is NULL, we ignore all check on the type/lazyness/default from Lexicon
1654
+        if ($lazy === null) {
1655
+            return true;
1656
+        }
1657
+
1658
+        /** @var ConfigLexiconEntry $configValue */
1659
+        $configValue = $configDetails['entries'][$key];
1660
+        $type &= ~self::VALUE_SENSITIVE;
1661
+
1662
+        $appConfigValueType = $configValue->getValueType()->toAppConfigFlag();
1663
+        if ($type === self::VALUE_MIXED) {
1664
+            $type = $appConfigValueType; // we overwrite if value was requested as mixed
1665
+        } elseif ($appConfigValueType !== $type) {
1666
+            throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1667
+        }
1668
+
1669
+        $lazy = $configValue->isLazy();
1670
+        // only look for default if needed, default from Lexicon got priority
1671
+        if ($default !== null) {
1672
+            $default = $configValue->getDefault($this->getLexiconPreset()) ?? $default;
1673
+        }
1674
+
1675
+        if ($configValue->isFlagged(self::FLAG_SENSITIVE)) {
1676
+            $type |= self::VALUE_SENSITIVE;
1677
+        }
1678
+        if ($configValue->isDeprecated()) {
1679
+            $this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
1680
+        }
1681
+
1682
+        return true;
1683
+    }
1684
+
1685
+    /**
1686
+     * manage ConfigLexicon behavior based on strictness set in IConfigLexicon
1687
+     *
1688
+     * @param ConfigLexiconStrictness|null $strictness
1689
+     * @param string $line
1690
+     *
1691
+     * @return bool TRUE if conflict can be fully ignored, FALSE if action should be not performed
1692
+     * @throws AppConfigUnknownKeyException if strictness implies exception
1693
+     * @see IConfigLexicon::getStrictness()
1694
+     */
1695
+    private function applyLexiconStrictness(
1696
+        ?ConfigLexiconStrictness $strictness,
1697
+        string $line = '',
1698
+    ): bool {
1699
+        if ($strictness === null) {
1700
+            return true;
1701
+        }
1702
+
1703
+        switch ($strictness) {
1704
+            case ConfigLexiconStrictness::IGNORE:
1705
+                return true;
1706
+            case ConfigLexiconStrictness::NOTICE:
1707
+                $this->logger->notice($line);
1708
+                return true;
1709
+            case ConfigLexiconStrictness::WARNING:
1710
+                $this->logger->warning($line);
1711
+                return false;
1712
+        }
1713
+
1714
+        throw new AppConfigUnknownKeyException($line);
1715
+    }
1716
+
1717
+    /**
1718
+     * extract details from registered $appId's config lexicon
1719
+     *
1720
+     * @param string $appId
1721
+     * @internal
1722
+     *
1723
+     * @return array{entries: array<string, ConfigLexiconEntry>, aliases: array<string, string>, strictness: ConfigLexiconStrictness}
1724
+     */
1725
+    public function getConfigDetailsFromLexicon(string $appId): array {
1726
+        if (!array_key_exists($appId, $this->configLexiconDetails)) {
1727
+            $entries = $aliases = [];
1728
+            $bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
1729
+            $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
1730
+            foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
1731
+                $entries[$configEntry->getKey()] = $configEntry;
1732
+                if ($configEntry->getRename() !== null) {
1733
+                    $aliases[$configEntry->getRename()] = $configEntry->getKey();
1734
+                }
1735
+            }
1736
+
1737
+            $this->configLexiconDetails[$appId] = [
1738
+                'entries' => $entries,
1739
+                'aliases' => $aliases,
1740
+                'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE
1741
+            ];
1742
+        }
1743
+
1744
+        return $this->configLexiconDetails[$appId];
1745
+    }
1746
+
1747
+    private function getLexiconEntry(string $appId, string $key): ?ConfigLexiconEntry {
1748
+        return $this->getConfigDetailsFromLexicon($appId)['entries'][$key] ?? null;
1749
+    }
1750
+
1751
+    /**
1752
+     * if set to TRUE, ignore aliases defined in Config Lexicon during the use of the methods of this class
1753
+     *
1754
+     * @internal
1755
+     */
1756
+    public function ignoreLexiconAliases(bool $ignore): void {
1757
+        $this->ignoreLexiconAliases = $ignore;
1758
+    }
1759
+
1760
+    private function getLexiconPreset(): Preset {
1761
+        if ($this->configLexiconPreset === null) {
1762
+            $this->configLexiconPreset = Preset::tryFrom($this->config->getSystemValueInt(ConfigManager::PRESET_CONFIGKEY, 0)) ?? Preset::NONE;
1763
+        }
1764
+
1765
+        return $this->configLexiconPreset;
1766
+    }
1767
+
1768
+    /**
1769
+     * Returns the installed versions of all apps
1770
+     *
1771
+     * @return array<string, string>
1772
+     */
1773
+    public function getAppInstalledVersions(bool $onlyEnabled = false): array {
1774
+        if ($this->appVersionsCache === null) {
1775
+            /** @var array<string, string> */
1776
+            $this->appVersionsCache = $this->searchValues('installed_version', false, IAppConfig::VALUE_STRING);
1777
+        }
1778
+        if ($onlyEnabled) {
1779
+            return array_filter(
1780
+                $this->appVersionsCache,
1781
+                fn (string $app): bool => $this->getValueString($app, 'enabled', 'no') !== 'no',
1782
+                ARRAY_FILTER_USE_KEY
1783
+            );
1784
+        }
1785
+        return $this->appVersionsCache;
1786
+    }
1787 1787
 }
Please login to merge, or discard this patch.
lib/public/IAppConfig.php 1 patch
Indentation   +499 added lines, -499 removed lines patch added patch discarded remove patch
@@ -30,503 +30,503 @@
 block discarded – undo
30 30
  * @since 29.0.0 - Supporting types and lazy loading
31 31
  */
32 32
 interface IAppConfig {
33
-	/** @since 29.0.0 */
34
-	public const VALUE_SENSITIVE = 1;
35
-	/** @since 29.0.0 */
36
-	public const VALUE_MIXED = 2;
37
-	/** @since 29.0.0 */
38
-	public const VALUE_STRING = 4;
39
-	/** @since 29.0.0 */
40
-	public const VALUE_INT = 8;
41
-	/** @since 29.0.0 */
42
-	public const VALUE_FLOAT = 16;
43
-	/** @since 29.0.0 */
44
-	public const VALUE_BOOL = 32;
45
-	/** @since 29.0.0 */
46
-	public const VALUE_ARRAY = 64;
47
-
48
-	/** @since 31.0.0 */
49
-	public const FLAG_SENSITIVE = 1;   // value is sensitive
50
-
51
-	/**
52
-	 * Get list of all apps that have at least one config value stored in database
53
-	 *
54
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
55
-	 *
56
-	 * @return list<string> list of app ids
57
-	 * @since 7.0.0
58
-	 */
59
-	public function getApps(): array;
60
-
61
-	/**
62
-	 * Returns all keys stored in database, related to an app.
63
-	 * Please note that the values are not returned.
64
-	 *
65
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
66
-	 *
67
-	 * @param string $app id of the app
68
-	 * @return list<string> list of stored config keys
69
-	 * @see searchKeys to avoid loading lazy config keys
70
-	 *
71
-	 * @since 29.0.0
72
-	 */
73
-	public function getKeys(string $app): array;
74
-
75
-	/**
76
-	 * Returns list of keys stored in database, related to an app.
77
-	 * Please note that the values are not returned.
78
-	 *
79
-	 * @param string $app id of the app
80
-	 * @param string $prefix returns only keys starting with this value
81
-	 * @param bool $lazy TRUE to search in lazy config keys
82
-	 *
83
-	 * @return list<string> list of stored config keys
84
-	 * @since 32.0.0
85
-	 */
86
-	public function searchKeys(string $app, string $prefix = '', bool $lazy = false): array;
87
-
88
-	/**
89
-	 * Check if a key exists in the list of stored config values.
90
-	 *
91
-	 * @param string $app id of the app
92
-	 * @param string $key config key
93
-	 * @param bool $lazy search within lazy loaded config
94
-	 *
95
-	 * @return bool TRUE if key exists
96
-	 * @since 29.0.0 Added the $lazy argument
97
-	 * @since 7.0.0
98
-	 */
99
-	public function hasKey(string $app, string $key, ?bool $lazy = false): bool;
100
-
101
-	/**
102
-	 * best way to see if a value is set as sensitive (not displayed in report)
103
-	 *
104
-	 * @param string $app id of the app
105
-	 * @param string $key config key
106
-	 * @param bool|null $lazy search within lazy loaded config
107
-	 *
108
-	 * @return bool TRUE if value is sensitive
109
-	 * @throws AppConfigUnknownKeyException if config key is not known
110
-	 * @since 29.0.0
111
-	 */
112
-	public function isSensitive(string $app, string $key, ?bool $lazy = false): bool;
113
-
114
-	/**
115
-	 * Returns if the config key stored in database is lazy loaded
116
-	 *
117
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
118
-	 *
119
-	 * @param string $app id of the app
120
-	 * @param string $key config key
121
-	 *
122
-	 * @return bool TRUE if config is lazy loaded
123
-	 * @throws AppConfigUnknownKeyException if config key is not known
124
-	 * @see IAppConfig for details about lazy loading
125
-	 * @since 29.0.0
126
-	 */
127
-	public function isLazy(string $app, string $key): bool;
128
-
129
-	/**
130
-	 * List all config values from an app with config key starting with $key.
131
-	 * Returns an array with config key as key, stored value as value.
132
-	 *
133
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
134
-	 *
135
-	 * @param string $app id of the app
136
-	 * @param string $prefix config keys prefix to search, can be empty.
137
-	 * @param bool $filtered filter sensitive config values
138
-	 *
139
-	 * @return array<string, string|int|float|bool|array> [configKey => configValue]
140
-	 * @since 29.0.0
141
-	 */
142
-	public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array;
143
-
144
-	/**
145
-	 * List all apps storing a specific config key and its stored value.
146
-	 * Returns an array with appId as key, stored value as value.
147
-	 *
148
-	 * @param string $key config key
149
-	 * @param bool $lazy search within lazy loaded config
150
-	 * @param int|null $typedAs enforce type for the returned values {@see self::VALUE_STRING} and others
151
-	 *
152
-	 * @return array<string, string|int|float|bool|array> [appId => configValue]
153
-	 * @since 29.0.0
154
-	 */
155
-	public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array;
156
-
157
-	/**
158
-	 * Get config value assigned to a config key.
159
-	 * If config key is not found in database, default value is returned.
160
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
161
-	 *
162
-	 * @param string $app id of the app
163
-	 * @param string $key config key
164
-	 * @param string $default default value
165
-	 * @param bool $lazy search within lazy loaded config
166
-	 *
167
-	 * @return string stored config value or $default if not set in database
168
-	 * @since 29.0.0
169
-	 * @see IAppConfig for explanation about lazy loading
170
-	 * @see getValueInt()
171
-	 * @see getValueFloat()
172
-	 * @see getValueBool()
173
-	 * @see getValueArray()
174
-	 */
175
-	public function getValueString(string $app, string $key, string $default = '', bool $lazy = false): string;
176
-
177
-	/**
178
-	 * Get config value assigned to a config key.
179
-	 * If config key is not found in database, default value is returned.
180
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
181
-	 *
182
-	 * @param string $app id of the app
183
-	 * @param string $key config key
184
-	 * @param int $default default value
185
-	 * @param bool $lazy search within lazy loaded config
186
-	 *
187
-	 * @return int stored config value or $default if not set in database
188
-	 * @since 29.0.0
189
-	 * @see IAppConfig for explanation about lazy loading
190
-	 * @see getValueString()
191
-	 * @see getValueFloat()
192
-	 * @see getValueBool()
193
-	 * @see getValueArray()
194
-	 */
195
-	public function getValueInt(string $app, string $key, int $default = 0, bool $lazy = false): int;
196
-
197
-	/**
198
-	 * Get config value assigned to a config key.
199
-	 * If config key is not found in database, default value is returned.
200
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
201
-	 *
202
-	 * @param string $app id of the app
203
-	 * @param string $key config key
204
-	 * @param float $default default value
205
-	 * @param bool $lazy search within lazy loaded config
206
-	 *
207
-	 * @return float stored config value or $default if not set in database
208
-	 * @since 29.0.0
209
-	 * @see IAppConfig for explanation about lazy loading
210
-	 * @see getValueString()
211
-	 * @see getValueInt()
212
-	 * @see getValueBool()
213
-	 * @see getValueArray()
214
-	 */
215
-	public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float;
216
-
217
-	/**
218
-	 * Get config value assigned to a config key.
219
-	 * If config key is not found in database, default value is returned.
220
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
221
-	 *
222
-	 * @param string $app id of the app
223
-	 * @param string $key config key
224
-	 * @param bool $default default value
225
-	 * @param bool $lazy search within lazy loaded config
226
-	 *
227
-	 * @return bool stored config value or $default if not set in database
228
-	 * @since 29.0.0
229
-	 * @see IAppConfig for explanation about lazy loading
230
-	 * @see getValueString()
231
-	 * @see getValueInt()
232
-	 * @see getValueFloat()
233
-	 * @see getValueArray()
234
-	 */
235
-	public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool;
236
-
237
-	/**
238
-	 * Get config value assigned to a config key.
239
-	 * If config key is not found in database, default value is returned.
240
-	 * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
241
-	 *
242
-	 * @param string $app id of the app
243
-	 * @param string $key config key
244
-	 * @param array $default default value
245
-	 * @param bool $lazy search within lazy loaded config
246
-	 *
247
-	 * @return array stored config value or $default if not set in database
248
-	 * @since 29.0.0
249
-	 * @see IAppConfig for explanation about lazy loading
250
-	 * @see getValueString()
251
-	 * @see getValueInt()
252
-	 * @see getValueFloat()
253
-	 * @see getValueBool()
254
-	 */
255
-	public function getValueArray(string $app, string $key, array $default = [], bool $lazy = false): array;
256
-
257
-	/**
258
-	 * returns the type of config value
259
-	 *
260
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
261
-	 *              unless lazy is set to false
262
-	 *
263
-	 * @param string $app id of the app
264
-	 * @param string $key config key
265
-	 * @param bool|null $lazy
266
-	 *
267
-	 * @return int
268
-	 * @throws AppConfigUnknownKeyException
269
-	 * @since 29.0.0
270
-	 * @see VALUE_STRING
271
-	 * @see VALUE_INT
272
-	 * @see VALUE_FLOAT
273
-	 * @see VALUE_BOOL
274
-	 * @see VALUE_ARRAY
275
-	 */
276
-	public function getValueType(string $app, string $key, ?bool $lazy = null): int;
277
-
278
-	/**
279
-	 * Store a config key and its value in database
280
-	 *
281
-	 * If config key is already known with the exact same config value, the database is not updated.
282
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
283
-	 *
284
-	 * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
285
-	 *
286
-	 * @param string $app id of the app
287
-	 * @param string $key config key
288
-	 * @param string $value config value
289
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
290
-	 * @param bool $lazy set config as lazy loaded
291
-	 *
292
-	 * @return bool TRUE if value was different, therefor updated in database
293
-	 * @since 29.0.0
294
-	 * @see IAppConfig for explanation about lazy loading
295
-	 * @see setValueInt()
296
-	 * @see setValueFloat()
297
-	 * @see setValueBool()
298
-	 * @see setValueArray()
299
-	 */
300
-	public function setValueString(string $app, string $key, string $value, bool $lazy = false, bool $sensitive = false): bool;
301
-
302
-	/**
303
-	 * Store a config key and its value in database
304
-	 *
305
-	 * When handling huge value around and/or above 2,147,483,647, a debug log will be generated
306
-	 * on 64bits system, as php int type reach its limit (and throw an exception) on 32bits when using huge numbers.
307
-	 *
308
-	 * When using huge numbers, it is advised to use {@see \OCP\Util::numericToNumber()} and {@see setValueString()}
309
-	 *
310
-	 * If config key is already known with the exact same config value, the database is not updated.
311
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
312
-	 *
313
-	 * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
314
-	 *
315
-	 * @param string $app id of the app
316
-	 * @param string $key config key
317
-	 * @param int $value config value
318
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
319
-	 * @param bool $lazy set config as lazy loaded
320
-	 *
321
-	 * @return bool TRUE if value was different, therefor updated in database
322
-	 * @since 29.0.0
323
-	 * @see IAppConfig for explanation about lazy loading
324
-	 * @see setValueString()
325
-	 * @see setValueFloat()
326
-	 * @see setValueBool()
327
-	 * @see setValueArray()
328
-	 */
329
-	public function setValueInt(string $app, string $key, int $value, bool $lazy = false, bool $sensitive = false): bool;
330
-
331
-	/**
332
-	 * Store a config key and its value in database.
333
-	 *
334
-	 * If config key is already known with the exact same config value, the database is not updated.
335
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
336
-	 *
337
-	 * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
338
-	 *
339
-	 * @param string $app id of the app
340
-	 * @param string $key config key
341
-	 * @param float $value config value
342
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
343
-	 * @param bool $lazy set config as lazy loaded
344
-	 *
345
-	 * @return bool TRUE if value was different, therefor updated in database
346
-	 * @since 29.0.0
347
-	 * @see IAppConfig for explanation about lazy loading
348
-	 * @see setValueString()
349
-	 * @see setValueInt()
350
-	 * @see setValueBool()
351
-	 * @see setValueArray()
352
-	 */
353
-	public function setValueFloat(string $app, string $key, float $value, bool $lazy = false, bool $sensitive = false): bool;
354
-
355
-	/**
356
-	 * Store a config key and its value in database
357
-	 *
358
-	 * If config key is already known with the exact same config value, the database is not updated.
359
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
360
-	 *
361
-	 * If config value was previously stored as lazy loaded, status cannot be altered without using {@see deleteKey()} first
362
-	 *
363
-	 * @param string $app id of the app
364
-	 * @param string $key config key
365
-	 * @param bool $value config value
366
-	 * @param bool $lazy set config as lazy loaded
367
-	 *
368
-	 * @return bool TRUE if value was different, therefor updated in database
369
-	 * @since 29.0.0
370
-	 * @see IAppConfig for explanation about lazy loading
371
-	 * @see setValueString()
372
-	 * @see setValueInt()
373
-	 * @see setValueFloat()
374
-	 * @see setValueArray()
375
-	 */
376
-	public function setValueBool(string $app, string $key, bool $value, bool $lazy = false): bool;
377
-
378
-	/**
379
-	 * Store a config key and its value in database
380
-	 *
381
-	 * If config key is already known with the exact same config value, the database is not updated.
382
-	 * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
383
-	 *
384
-	 * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
385
-	 *
386
-	 * @param string $app id of the app
387
-	 * @param string $key config key
388
-	 * @param array $value config value
389
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
390
-	 * @param bool $lazy set config as lazy loaded
391
-	 *
392
-	 * @return bool TRUE if value was different, therefor updated in database
393
-	 * @since 29.0.0
394
-	 * @see IAppConfig for explanation about lazy loading
395
-	 * @see setValueString()
396
-	 * @see setValueInt()
397
-	 * @see setValueFloat()
398
-	 * @see setValueBool()
399
-	 */
400
-	public function setValueArray(string $app, string $key, array $value, bool $lazy = false, bool $sensitive = false): bool;
401
-
402
-	/**
403
-	 * switch sensitive status of a config value
404
-	 *
405
-	 * **WARNING:** ignore lazy filtering, all config values are loaded from database
406
-	 *
407
-	 * @param string $app id of the app
408
-	 * @param string $key config key
409
-	 * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
410
-	 *
411
-	 * @return bool TRUE if database update were necessary
412
-	 * @since 29.0.0
413
-	 */
414
-	public function updateSensitive(string $app, string $key, bool $sensitive): bool;
415
-
416
-	/**
417
-	 * switch lazy loading status of a config value
418
-	 *
419
-	 * @param string $app id of the app
420
-	 * @param string $key config key
421
-	 * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
422
-	 *
423
-	 * @return bool TRUE if database update was necessary
424
-	 * @since 29.0.0
425
-	 */
426
-	public function updateLazy(string $app, string $key, bool $lazy): bool;
427
-
428
-	/**
429
-	 * returns an array contains details about a config value
430
-	 *
431
-	 * ```
432
-	 * [
433
-	 *   "app" => "myapp",
434
-	 *   "key" => "mykey",
435
-	 *   "value" => "its_value",
436
-	 *   "lazy" => false,
437
-	 *   "type" => 4,
438
-	 *   "typeString" => "string",
439
-	 *   'sensitive' => true
440
-	 * ]
441
-	 * ```
442
-	 *
443
-	 * @param string $app id of the app
444
-	 * @param string $key config key
445
-	 *
446
-	 * @return array
447
-	 * @throws AppConfigUnknownKeyException if config key is not known in database
448
-	 * @since 29.0.0
449
-	 */
450
-	public function getDetails(string $app, string $key): array;
451
-
452
-	/**
453
-	 * Convert string like 'string', 'integer', 'float', 'bool' or 'array' to
454
-	 * to bitflag {@see VALUE_STRING}, {@see VALUE_INT}, {@see VALUE_FLOAT},
455
-	 * {@see VALUE_BOOL} and {@see VALUE_ARRAY}
456
-	 *
457
-	 * @param string $type
458
-	 *
459
-	 * @return int
460
-	 * @since 29.0.0
461
-	 */
462
-	public function convertTypeToInt(string $type): int;
463
-
464
-	/**
465
-	 * Convert bitflag {@see VALUE_STRING}, {@see VALUE_INT}, {@see VALUE_FLOAT},
466
-	 * {@see VALUE_BOOL} and {@see VALUE_ARRAY} to human-readable string
467
-	 *
468
-	 * @param int $type
469
-	 *
470
-	 * @return string
471
-	 * @since 29.0.0
472
-	 */
473
-	public function convertTypeToString(int $type): string;
474
-
475
-	/**
476
-	 * Delete single config key from database.
477
-	 *
478
-	 * @param string $app id of the app
479
-	 * @param string $key config key
480
-	 * @since 29.0.0
481
-	 */
482
-	public function deleteKey(string $app, string $key): void;
483
-
484
-	/**
485
-	 * delete all config keys linked to an app
486
-	 *
487
-	 * @param string $app id of the app
488
-	 * @since 29.0.0
489
-	 */
490
-	public function deleteApp(string $app): void;
491
-
492
-	/**
493
-	 * Clear the cache.
494
-	 *
495
-	 * The cache will be rebuilt only the next time a config value is requested.
496
-	 *
497
-	 * @param bool $reload set to TRUE to refill cache instantly after clearing it
498
-	 * @since 29.0.0
499
-	 */
500
-	public function clearCache(bool $reload = false): void;
501
-
502
-	/**
503
-	 * get multiply values, either the app or key can be used as wildcard by setting it to false
504
-	 *
505
-	 * @param string|false $key
506
-	 * @param string|false $app
507
-	 *
508
-	 * @return array|false
509
-	 * @since 7.0.0
510
-	 * @deprecated 29.0.0 Use {@see getAllValues()} or {@see searchValues()}
511
-	 */
512
-	public function getValues($app, $key);
513
-
514
-	/**
515
-	 * get all values of the app or and filters out sensitive data
516
-	 *
517
-	 * @param string $app
518
-	 *
519
-	 * @return array
520
-	 * @since 12.0.0
521
-	 * @deprecated 29.0.0 Use {@see getAllValues()} or {@see searchValues()}
522
-	 */
523
-	public function getFilteredValues($app);
524
-
525
-	/**
526
-	 * Returns the installed version of all apps
527
-	 *
528
-	 * @return array<string, string>
529
-	 * @since 32.0.0
530
-	 */
531
-	public function getAppInstalledVersions(bool $onlyEnabled = false): array;
33
+    /** @since 29.0.0 */
34
+    public const VALUE_SENSITIVE = 1;
35
+    /** @since 29.0.0 */
36
+    public const VALUE_MIXED = 2;
37
+    /** @since 29.0.0 */
38
+    public const VALUE_STRING = 4;
39
+    /** @since 29.0.0 */
40
+    public const VALUE_INT = 8;
41
+    /** @since 29.0.0 */
42
+    public const VALUE_FLOAT = 16;
43
+    /** @since 29.0.0 */
44
+    public const VALUE_BOOL = 32;
45
+    /** @since 29.0.0 */
46
+    public const VALUE_ARRAY = 64;
47
+
48
+    /** @since 31.0.0 */
49
+    public const FLAG_SENSITIVE = 1;   // value is sensitive
50
+
51
+    /**
52
+     * Get list of all apps that have at least one config value stored in database
53
+     *
54
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
55
+     *
56
+     * @return list<string> list of app ids
57
+     * @since 7.0.0
58
+     */
59
+    public function getApps(): array;
60
+
61
+    /**
62
+     * Returns all keys stored in database, related to an app.
63
+     * Please note that the values are not returned.
64
+     *
65
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
66
+     *
67
+     * @param string $app id of the app
68
+     * @return list<string> list of stored config keys
69
+     * @see searchKeys to avoid loading lazy config keys
70
+     *
71
+     * @since 29.0.0
72
+     */
73
+    public function getKeys(string $app): array;
74
+
75
+    /**
76
+     * Returns list of keys stored in database, related to an app.
77
+     * Please note that the values are not returned.
78
+     *
79
+     * @param string $app id of the app
80
+     * @param string $prefix returns only keys starting with this value
81
+     * @param bool $lazy TRUE to search in lazy config keys
82
+     *
83
+     * @return list<string> list of stored config keys
84
+     * @since 32.0.0
85
+     */
86
+    public function searchKeys(string $app, string $prefix = '', bool $lazy = false): array;
87
+
88
+    /**
89
+     * Check if a key exists in the list of stored config values.
90
+     *
91
+     * @param string $app id of the app
92
+     * @param string $key config key
93
+     * @param bool $lazy search within lazy loaded config
94
+     *
95
+     * @return bool TRUE if key exists
96
+     * @since 29.0.0 Added the $lazy argument
97
+     * @since 7.0.0
98
+     */
99
+    public function hasKey(string $app, string $key, ?bool $lazy = false): bool;
100
+
101
+    /**
102
+     * best way to see if a value is set as sensitive (not displayed in report)
103
+     *
104
+     * @param string $app id of the app
105
+     * @param string $key config key
106
+     * @param bool|null $lazy search within lazy loaded config
107
+     *
108
+     * @return bool TRUE if value is sensitive
109
+     * @throws AppConfigUnknownKeyException if config key is not known
110
+     * @since 29.0.0
111
+     */
112
+    public function isSensitive(string $app, string $key, ?bool $lazy = false): bool;
113
+
114
+    /**
115
+     * Returns if the config key stored in database is lazy loaded
116
+     *
117
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
118
+     *
119
+     * @param string $app id of the app
120
+     * @param string $key config key
121
+     *
122
+     * @return bool TRUE if config is lazy loaded
123
+     * @throws AppConfigUnknownKeyException if config key is not known
124
+     * @see IAppConfig for details about lazy loading
125
+     * @since 29.0.0
126
+     */
127
+    public function isLazy(string $app, string $key): bool;
128
+
129
+    /**
130
+     * List all config values from an app with config key starting with $key.
131
+     * Returns an array with config key as key, stored value as value.
132
+     *
133
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
134
+     *
135
+     * @param string $app id of the app
136
+     * @param string $prefix config keys prefix to search, can be empty.
137
+     * @param bool $filtered filter sensitive config values
138
+     *
139
+     * @return array<string, string|int|float|bool|array> [configKey => configValue]
140
+     * @since 29.0.0
141
+     */
142
+    public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array;
143
+
144
+    /**
145
+     * List all apps storing a specific config key and its stored value.
146
+     * Returns an array with appId as key, stored value as value.
147
+     *
148
+     * @param string $key config key
149
+     * @param bool $lazy search within lazy loaded config
150
+     * @param int|null $typedAs enforce type for the returned values {@see self::VALUE_STRING} and others
151
+     *
152
+     * @return array<string, string|int|float|bool|array> [appId => configValue]
153
+     * @since 29.0.0
154
+     */
155
+    public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array;
156
+
157
+    /**
158
+     * Get config value assigned to a config key.
159
+     * If config key is not found in database, default value is returned.
160
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
161
+     *
162
+     * @param string $app id of the app
163
+     * @param string $key config key
164
+     * @param string $default default value
165
+     * @param bool $lazy search within lazy loaded config
166
+     *
167
+     * @return string stored config value or $default if not set in database
168
+     * @since 29.0.0
169
+     * @see IAppConfig for explanation about lazy loading
170
+     * @see getValueInt()
171
+     * @see getValueFloat()
172
+     * @see getValueBool()
173
+     * @see getValueArray()
174
+     */
175
+    public function getValueString(string $app, string $key, string $default = '', bool $lazy = false): string;
176
+
177
+    /**
178
+     * Get config value assigned to a config key.
179
+     * If config key is not found in database, default value is returned.
180
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
181
+     *
182
+     * @param string $app id of the app
183
+     * @param string $key config key
184
+     * @param int $default default value
185
+     * @param bool $lazy search within lazy loaded config
186
+     *
187
+     * @return int stored config value or $default if not set in database
188
+     * @since 29.0.0
189
+     * @see IAppConfig for explanation about lazy loading
190
+     * @see getValueString()
191
+     * @see getValueFloat()
192
+     * @see getValueBool()
193
+     * @see getValueArray()
194
+     */
195
+    public function getValueInt(string $app, string $key, int $default = 0, bool $lazy = false): int;
196
+
197
+    /**
198
+     * Get config value assigned to a config key.
199
+     * If config key is not found in database, default value is returned.
200
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
201
+     *
202
+     * @param string $app id of the app
203
+     * @param string $key config key
204
+     * @param float $default default value
205
+     * @param bool $lazy search within lazy loaded config
206
+     *
207
+     * @return float stored config value or $default if not set in database
208
+     * @since 29.0.0
209
+     * @see IAppConfig for explanation about lazy loading
210
+     * @see getValueString()
211
+     * @see getValueInt()
212
+     * @see getValueBool()
213
+     * @see getValueArray()
214
+     */
215
+    public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float;
216
+
217
+    /**
218
+     * Get config value assigned to a config key.
219
+     * If config key is not found in database, default value is returned.
220
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
221
+     *
222
+     * @param string $app id of the app
223
+     * @param string $key config key
224
+     * @param bool $default default value
225
+     * @param bool $lazy search within lazy loaded config
226
+     *
227
+     * @return bool stored config value or $default if not set in database
228
+     * @since 29.0.0
229
+     * @see IAppConfig for explanation about lazy loading
230
+     * @see getValueString()
231
+     * @see getValueInt()
232
+     * @see getValueFloat()
233
+     * @see getValueArray()
234
+     */
235
+    public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool;
236
+
237
+    /**
238
+     * Get config value assigned to a config key.
239
+     * If config key is not found in database, default value is returned.
240
+     * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE.
241
+     *
242
+     * @param string $app id of the app
243
+     * @param string $key config key
244
+     * @param array $default default value
245
+     * @param bool $lazy search within lazy loaded config
246
+     *
247
+     * @return array stored config value or $default if not set in database
248
+     * @since 29.0.0
249
+     * @see IAppConfig for explanation about lazy loading
250
+     * @see getValueString()
251
+     * @see getValueInt()
252
+     * @see getValueFloat()
253
+     * @see getValueBool()
254
+     */
255
+    public function getValueArray(string $app, string $key, array $default = [], bool $lazy = false): array;
256
+
257
+    /**
258
+     * returns the type of config value
259
+     *
260
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
261
+     *              unless lazy is set to false
262
+     *
263
+     * @param string $app id of the app
264
+     * @param string $key config key
265
+     * @param bool|null $lazy
266
+     *
267
+     * @return int
268
+     * @throws AppConfigUnknownKeyException
269
+     * @since 29.0.0
270
+     * @see VALUE_STRING
271
+     * @see VALUE_INT
272
+     * @see VALUE_FLOAT
273
+     * @see VALUE_BOOL
274
+     * @see VALUE_ARRAY
275
+     */
276
+    public function getValueType(string $app, string $key, ?bool $lazy = null): int;
277
+
278
+    /**
279
+     * Store a config key and its value in database
280
+     *
281
+     * If config key is already known with the exact same config value, the database is not updated.
282
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
283
+     *
284
+     * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
285
+     *
286
+     * @param string $app id of the app
287
+     * @param string $key config key
288
+     * @param string $value config value
289
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
290
+     * @param bool $lazy set config as lazy loaded
291
+     *
292
+     * @return bool TRUE if value was different, therefor updated in database
293
+     * @since 29.0.0
294
+     * @see IAppConfig for explanation about lazy loading
295
+     * @see setValueInt()
296
+     * @see setValueFloat()
297
+     * @see setValueBool()
298
+     * @see setValueArray()
299
+     */
300
+    public function setValueString(string $app, string $key, string $value, bool $lazy = false, bool $sensitive = false): bool;
301
+
302
+    /**
303
+     * Store a config key and its value in database
304
+     *
305
+     * When handling huge value around and/or above 2,147,483,647, a debug log will be generated
306
+     * on 64bits system, as php int type reach its limit (and throw an exception) on 32bits when using huge numbers.
307
+     *
308
+     * When using huge numbers, it is advised to use {@see \OCP\Util::numericToNumber()} and {@see setValueString()}
309
+     *
310
+     * If config key is already known with the exact same config value, the database is not updated.
311
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
312
+     *
313
+     * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
314
+     *
315
+     * @param string $app id of the app
316
+     * @param string $key config key
317
+     * @param int $value config value
318
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
319
+     * @param bool $lazy set config as lazy loaded
320
+     *
321
+     * @return bool TRUE if value was different, therefor updated in database
322
+     * @since 29.0.0
323
+     * @see IAppConfig for explanation about lazy loading
324
+     * @see setValueString()
325
+     * @see setValueFloat()
326
+     * @see setValueBool()
327
+     * @see setValueArray()
328
+     */
329
+    public function setValueInt(string $app, string $key, int $value, bool $lazy = false, bool $sensitive = false): bool;
330
+
331
+    /**
332
+     * Store a config key and its value in database.
333
+     *
334
+     * If config key is already known with the exact same config value, the database is not updated.
335
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
336
+     *
337
+     * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
338
+     *
339
+     * @param string $app id of the app
340
+     * @param string $key config key
341
+     * @param float $value config value
342
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
343
+     * @param bool $lazy set config as lazy loaded
344
+     *
345
+     * @return bool TRUE if value was different, therefor updated in database
346
+     * @since 29.0.0
347
+     * @see IAppConfig for explanation about lazy loading
348
+     * @see setValueString()
349
+     * @see setValueInt()
350
+     * @see setValueBool()
351
+     * @see setValueArray()
352
+     */
353
+    public function setValueFloat(string $app, string $key, float $value, bool $lazy = false, bool $sensitive = false): bool;
354
+
355
+    /**
356
+     * Store a config key and its value in database
357
+     *
358
+     * If config key is already known with the exact same config value, the database is not updated.
359
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
360
+     *
361
+     * If config value was previously stored as lazy loaded, status cannot be altered without using {@see deleteKey()} first
362
+     *
363
+     * @param string $app id of the app
364
+     * @param string $key config key
365
+     * @param bool $value config value
366
+     * @param bool $lazy set config as lazy loaded
367
+     *
368
+     * @return bool TRUE if value was different, therefor updated in database
369
+     * @since 29.0.0
370
+     * @see IAppConfig for explanation about lazy loading
371
+     * @see setValueString()
372
+     * @see setValueInt()
373
+     * @see setValueFloat()
374
+     * @see setValueArray()
375
+     */
376
+    public function setValueBool(string $app, string $key, bool $value, bool $lazy = false): bool;
377
+
378
+    /**
379
+     * Store a config key and its value in database
380
+     *
381
+     * If config key is already known with the exact same config value, the database is not updated.
382
+     * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded.
383
+     *
384
+     * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first
385
+     *
386
+     * @param string $app id of the app
387
+     * @param string $key config key
388
+     * @param array $value config value
389
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
390
+     * @param bool $lazy set config as lazy loaded
391
+     *
392
+     * @return bool TRUE if value was different, therefor updated in database
393
+     * @since 29.0.0
394
+     * @see IAppConfig for explanation about lazy loading
395
+     * @see setValueString()
396
+     * @see setValueInt()
397
+     * @see setValueFloat()
398
+     * @see setValueBool()
399
+     */
400
+    public function setValueArray(string $app, string $key, array $value, bool $lazy = false, bool $sensitive = false): bool;
401
+
402
+    /**
403
+     * switch sensitive status of a config value
404
+     *
405
+     * **WARNING:** ignore lazy filtering, all config values are loaded from database
406
+     *
407
+     * @param string $app id of the app
408
+     * @param string $key config key
409
+     * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
410
+     *
411
+     * @return bool TRUE if database update were necessary
412
+     * @since 29.0.0
413
+     */
414
+    public function updateSensitive(string $app, string $key, bool $sensitive): bool;
415
+
416
+    /**
417
+     * switch lazy loading status of a config value
418
+     *
419
+     * @param string $app id of the app
420
+     * @param string $key config key
421
+     * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
422
+     *
423
+     * @return bool TRUE if database update was necessary
424
+     * @since 29.0.0
425
+     */
426
+    public function updateLazy(string $app, string $key, bool $lazy): bool;
427
+
428
+    /**
429
+     * returns an array contains details about a config value
430
+     *
431
+     * ```
432
+     * [
433
+     *   "app" => "myapp",
434
+     *   "key" => "mykey",
435
+     *   "value" => "its_value",
436
+     *   "lazy" => false,
437
+     *   "type" => 4,
438
+     *   "typeString" => "string",
439
+     *   'sensitive' => true
440
+     * ]
441
+     * ```
442
+     *
443
+     * @param string $app id of the app
444
+     * @param string $key config key
445
+     *
446
+     * @return array
447
+     * @throws AppConfigUnknownKeyException if config key is not known in database
448
+     * @since 29.0.0
449
+     */
450
+    public function getDetails(string $app, string $key): array;
451
+
452
+    /**
453
+     * Convert string like 'string', 'integer', 'float', 'bool' or 'array' to
454
+     * to bitflag {@see VALUE_STRING}, {@see VALUE_INT}, {@see VALUE_FLOAT},
455
+     * {@see VALUE_BOOL} and {@see VALUE_ARRAY}
456
+     *
457
+     * @param string $type
458
+     *
459
+     * @return int
460
+     * @since 29.0.0
461
+     */
462
+    public function convertTypeToInt(string $type): int;
463
+
464
+    /**
465
+     * Convert bitflag {@see VALUE_STRING}, {@see VALUE_INT}, {@see VALUE_FLOAT},
466
+     * {@see VALUE_BOOL} and {@see VALUE_ARRAY} to human-readable string
467
+     *
468
+     * @param int $type
469
+     *
470
+     * @return string
471
+     * @since 29.0.0
472
+     */
473
+    public function convertTypeToString(int $type): string;
474
+
475
+    /**
476
+     * Delete single config key from database.
477
+     *
478
+     * @param string $app id of the app
479
+     * @param string $key config key
480
+     * @since 29.0.0
481
+     */
482
+    public function deleteKey(string $app, string $key): void;
483
+
484
+    /**
485
+     * delete all config keys linked to an app
486
+     *
487
+     * @param string $app id of the app
488
+     * @since 29.0.0
489
+     */
490
+    public function deleteApp(string $app): void;
491
+
492
+    /**
493
+     * Clear the cache.
494
+     *
495
+     * The cache will be rebuilt only the next time a config value is requested.
496
+     *
497
+     * @param bool $reload set to TRUE to refill cache instantly after clearing it
498
+     * @since 29.0.0
499
+     */
500
+    public function clearCache(bool $reload = false): void;
501
+
502
+    /**
503
+     * get multiply values, either the app or key can be used as wildcard by setting it to false
504
+     *
505
+     * @param string|false $key
506
+     * @param string|false $app
507
+     *
508
+     * @return array|false
509
+     * @since 7.0.0
510
+     * @deprecated 29.0.0 Use {@see getAllValues()} or {@see searchValues()}
511
+     */
512
+    public function getValues($app, $key);
513
+
514
+    /**
515
+     * get all values of the app or and filters out sensitive data
516
+     *
517
+     * @param string $app
518
+     *
519
+     * @return array
520
+     * @since 12.0.0
521
+     * @deprecated 29.0.0 Use {@see getAllValues()} or {@see searchValues()}
522
+     */
523
+    public function getFilteredValues($app);
524
+
525
+    /**
526
+     * Returns the installed version of all apps
527
+     *
528
+     * @return array<string, string>
529
+     * @since 32.0.0
530
+     */
531
+    public function getAppInstalledVersions(bool $onlyEnabled = false): array;
532 532
 }
Please login to merge, or discard this patch.