Completed
Push — master ( d0a5ee...b8b229 )
by John
40:50 queued 14s
created
tests/lib/AppTest.php 1 patch
Indentation   +612 added lines, -612 removed lines patch added patch discarded remove patch
@@ -28,616 +28,616 @@
 block discarded – undo
28 28
  * @group DB
29 29
  */
30 30
 class AppTest extends \Test\TestCase {
31
-	public const TEST_USER1 = 'user1';
32
-	public const TEST_USER2 = 'user2';
33
-	public const TEST_USER3 = 'user3';
34
-	public const TEST_GROUP1 = 'group1';
35
-	public const TEST_GROUP2 = 'group2';
36
-
37
-	public static function appVersionsProvider(): array {
38
-		return [
39
-			// exact match
40
-			[
41
-				'6.0.0.0',
42
-				[
43
-					'requiremin' => '6.0',
44
-					'requiremax' => '6.0',
45
-				],
46
-				true
47
-			],
48
-			// in-between match
49
-			[
50
-				'6.0.0.0',
51
-				[
52
-					'requiremin' => '5.0',
53
-					'requiremax' => '7.0',
54
-				],
55
-				true
56
-			],
57
-			// app too old
58
-			[
59
-				'6.0.0.0',
60
-				[
61
-					'requiremin' => '5.0',
62
-					'requiremax' => '5.0',
63
-				],
64
-				false
65
-			],
66
-			// app too new
67
-			[
68
-				'5.0.0.0',
69
-				[
70
-					'requiremin' => '6.0',
71
-					'requiremax' => '6.0',
72
-				],
73
-				false
74
-			],
75
-			// only min specified
76
-			[
77
-				'6.0.0.0',
78
-				[
79
-					'requiremin' => '6.0',
80
-				],
81
-				true
82
-			],
83
-			// only min specified fail
84
-			[
85
-				'5.0.0.0',
86
-				[
87
-					'requiremin' => '6.0',
88
-				],
89
-				false
90
-			],
91
-			// only min specified legacy
92
-			[
93
-				'6.0.0.0',
94
-				[
95
-					'require' => '6.0',
96
-				],
97
-				true
98
-			],
99
-			// only min specified legacy fail
100
-			[
101
-				'4.0.0.0',
102
-				[
103
-					'require' => '6.0',
104
-				],
105
-				false
106
-			],
107
-			// only max specified
108
-			[
109
-				'5.0.0.0',
110
-				[
111
-					'requiremax' => '6.0',
112
-				],
113
-				true
114
-			],
115
-			// only max specified fail
116
-			[
117
-				'7.0.0.0',
118
-				[
119
-					'requiremax' => '6.0',
120
-				],
121
-				false
122
-			],
123
-			// variations of versions
124
-			// single OC number
125
-			[
126
-				'4',
127
-				[
128
-					'require' => '4.0',
129
-				],
130
-				true
131
-			],
132
-			// multiple OC number
133
-			[
134
-				'4.3.1',
135
-				[
136
-					'require' => '4.3',
137
-				],
138
-				true
139
-			],
140
-			// single app number
141
-			[
142
-				'4',
143
-				[
144
-					'require' => '4',
145
-				],
146
-				true
147
-			],
148
-			// single app number fail
149
-			[
150
-				'4.3',
151
-				[
152
-					'require' => '5',
153
-				],
154
-				false
155
-			],
156
-			// complex
157
-			[
158
-				'5.0.0',
159
-				[
160
-					'require' => '4.5.1',
161
-				],
162
-				true
163
-			],
164
-			// complex fail
165
-			[
166
-				'4.3.1',
167
-				[
168
-					'require' => '4.3.2',
169
-				],
170
-				false
171
-			],
172
-			// two numbers
173
-			[
174
-				'4.3.1',
175
-				[
176
-					'require' => '4.4',
177
-				],
178
-				false
179
-			],
180
-			// one number fail
181
-			[
182
-				'4.3.1',
183
-				[
184
-					'require' => '5',
185
-				],
186
-				false
187
-			],
188
-			// pre-alpha app
189
-			[
190
-				'5.0.3',
191
-				[
192
-					'require' => '4.93',
193
-				],
194
-				true
195
-			],
196
-			// pre-alpha OC
197
-			[
198
-				'6.90.0.2',
199
-				[
200
-					'require' => '6.90',
201
-				],
202
-				true
203
-			],
204
-			// pre-alpha OC max
205
-			[
206
-				'6.90.0.2',
207
-				[
208
-					'requiremax' => '7',
209
-				],
210
-				true
211
-			],
212
-			// expect same major number match
213
-			[
214
-				'5.0.3',
215
-				[
216
-					'require' => '5',
217
-				],
218
-				true
219
-			],
220
-			// expect same major number match
221
-			[
222
-				'5.0.3',
223
-				[
224
-					'requiremax' => '5',
225
-				],
226
-				true
227
-			],
228
-			// dependencies versions before require*
229
-			[
230
-				'6.0.0.0',
231
-				[
232
-					'requiremin' => '5.0',
233
-					'requiremax' => '7.0',
234
-					'dependencies' => [
235
-						'owncloud' => [
236
-							'@attributes' => [
237
-								'min-version' => '7.0',
238
-								'max-version' => '7.0',
239
-							],
240
-						],
241
-					],
242
-				],
243
-				false
244
-			],
245
-			[
246
-				'6.0.0.0',
247
-				[
248
-					'requiremin' => '5.0',
249
-					'requiremax' => '7.0',
250
-					'dependencies' => [
251
-						'owncloud' => [
252
-							'@attributes' => [
253
-								'min-version' => '5.0',
254
-								'max-version' => '5.0',
255
-							],
256
-						],
257
-					],
258
-				],
259
-				false
260
-			],
261
-			[
262
-				'6.0.0.0',
263
-				[
264
-					'requiremin' => '5.0',
265
-					'requiremax' => '5.0',
266
-					'dependencies' => [
267
-						'owncloud' => [
268
-							'@attributes' => [
269
-								'min-version' => '5.0',
270
-								'max-version' => '7.0',
271
-							],
272
-						],
273
-					],
274
-				],
275
-				true
276
-			],
277
-			[
278
-				'9.2.0.0',
279
-				[
280
-					'dependencies' => [
281
-						'owncloud' => [
282
-							'@attributes' => [
283
-								'min-version' => '9.0',
284
-								'max-version' => '9.1',
285
-							],
286
-						],
287
-						'nextcloud' => [
288
-							'@attributes' => [
289
-								'min-version' => '9.1',
290
-								'max-version' => '9.2',
291
-							],
292
-						],
293
-					],
294
-				],
295
-				true
296
-			],
297
-			[
298
-				'9.2.0.0',
299
-				[
300
-					'dependencies' => [
301
-						'nextcloud' => [
302
-							'@attributes' => [
303
-								'min-version' => '9.1',
304
-								'max-version' => '9.2',
305
-							],
306
-						],
307
-					],
308
-				],
309
-				true
310
-			],
311
-		];
312
-	}
313
-
314
-	/**
315
-	 * @dataProvider appVersionsProvider
316
-	 */
317
-	public function testIsAppCompatible($ocVersion, $appInfo, $expectedResult): void {
318
-		$this->assertEquals($expectedResult, \OC_App::isAppCompatible($ocVersion, $appInfo));
319
-	}
320
-
321
-	/**
322
-	 * Tests that the app order is correct
323
-	 */
324
-	public function testGetEnabledAppsIsSorted(): void {
325
-		$apps = \OC_App::getEnabledApps();
326
-		// copy array
327
-		$sortedApps = $apps;
328
-		sort($sortedApps);
329
-		// 'files' is always on top
330
-		unset($sortedApps[array_search('files', $sortedApps)]);
331
-		array_unshift($sortedApps, 'files');
332
-		$this->assertEquals($sortedApps, $apps);
333
-	}
334
-
335
-	/**
336
-	 * Providers for the app config values
337
-	 */
338
-	public static function appConfigValuesProvider(): array {
339
-		return [
340
-			// logged in user1
341
-			[
342
-				self::TEST_USER1,
343
-				[
344
-					'files',
345
-					'app1',
346
-					'app3',
347
-					'appforgroup1',
348
-					'appforgroup12',
349
-					'cloud_federation_api',
350
-					'dav',
351
-					'federatedfilesharing',
352
-					'lookup_server_connector',
353
-					'oauth2',
354
-					'profile',
355
-					'provisioning_api',
356
-					'settings',
357
-					'theming',
358
-					'twofactor_backupcodes',
359
-					'viewer',
360
-					'workflowengine',
361
-				],
362
-				false
363
-			],
364
-			// logged in user2
365
-			[
366
-				self::TEST_USER2,
367
-				[
368
-					'files',
369
-					'app1',
370
-					'app3',
371
-					'appforgroup12',
372
-					'appforgroup2',
373
-					'cloud_federation_api',
374
-					'dav',
375
-					'federatedfilesharing',
376
-					'lookup_server_connector',
377
-					'oauth2',
378
-					'profile',
379
-					'provisioning_api',
380
-					'settings',
381
-					'theming',
382
-					'twofactor_backupcodes',
383
-					'viewer',
384
-					'workflowengine',
385
-				],
386
-				false
387
-			],
388
-			// logged in user3
389
-			[
390
-				self::TEST_USER3,
391
-				[
392
-					'files',
393
-					'app1',
394
-					'app3',
395
-					'appforgroup1',
396
-					'appforgroup12',
397
-					'appforgroup2',
398
-					'cloud_federation_api',
399
-					'dav',
400
-					'federatedfilesharing',
401
-					'lookup_server_connector',
402
-					'oauth2',
403
-					'profile',
404
-					'provisioning_api',
405
-					'settings',
406
-					'theming',
407
-					'twofactor_backupcodes',
408
-					'viewer',
409
-					'workflowengine',
410
-				],
411
-				false
412
-			],
413
-			//  no user, returns all apps
414
-			[
415
-				null,
416
-				[
417
-					'files',
418
-					'app1',
419
-					'app3',
420
-					'appforgroup1',
421
-					'appforgroup12',
422
-					'appforgroup2',
423
-					'cloud_federation_api',
424
-					'dav',
425
-					'federatedfilesharing',
426
-					'lookup_server_connector',
427
-					'oauth2',
428
-					'profile',
429
-					'provisioning_api',
430
-					'settings',
431
-					'theming',
432
-					'twofactor_backupcodes',
433
-					'viewer',
434
-					'workflowengine',
435
-				],
436
-				false,
437
-			],
438
-			//  user given, but ask for all
439
-			[
440
-				self::TEST_USER1,
441
-				[
442
-					'files',
443
-					'app1',
444
-					'app3',
445
-					'appforgroup1',
446
-					'appforgroup12',
447
-					'appforgroup2',
448
-					'cloud_federation_api',
449
-					'dav',
450
-					'federatedfilesharing',
451
-					'lookup_server_connector',
452
-					'oauth2',
453
-					'profile',
454
-					'provisioning_api',
455
-					'settings',
456
-					'theming',
457
-					'twofactor_backupcodes',
458
-					'viewer',
459
-					'workflowengine',
460
-				],
461
-				true,
462
-			],
463
-		];
464
-	}
465
-
466
-	/**
467
-	 * Test enabled apps
468
-	 *
469
-	 * @dataProvider appConfigValuesProvider
470
-	 */
471
-	public function testEnabledApps($user, $expectedApps, $forceAll): void {
472
-		$userManager = \OCP\Server::get(IUserManager::class);
473
-		$groupManager = \OCP\Server::get(IGroupManager::class);
474
-		$user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
475
-		$user2 = $userManager->createUser(self::TEST_USER2, 'NotAnEasyPassword123456_');
476
-		$user3 = $userManager->createUser(self::TEST_USER3, 'NotAnEasyPassword123456?');
477
-
478
-		$group1 = $groupManager->createGroup(self::TEST_GROUP1);
479
-		$group1->addUser($user1);
480
-		$group1->addUser($user3);
481
-		$group2 = $groupManager->createGroup(self::TEST_GROUP2);
482
-		$group2->addUser($user2);
483
-		$group2->addUser($user3);
484
-
485
-		\OC_User::setUserId($user);
486
-
487
-		$this->setupAppConfigMock()->expects($this->once())
488
-			->method('searchValues')
489
-			->willReturn(
490
-				[
491
-					'app3' => 'yes',
492
-					'app2' => 'no',
493
-					'app1' => 'yes',
494
-					'appforgroup1' => '["group1"]',
495
-					'appforgroup2' => '["group2"]',
496
-					'appforgroup12' => '["group2","group1"]',
497
-				]
498
-			);
499
-
500
-		$apps = \OC_App::getEnabledApps(false, $forceAll);
501
-
502
-		$this->restoreAppConfig();
503
-		\OC_User::setUserId(null);
504
-
505
-		$user1->delete();
506
-		$user2->delete();
507
-		$user3->delete();
508
-
509
-		$group1->delete();
510
-		$group2->delete();
511
-
512
-		$this->assertEquals($expectedApps, $apps);
513
-	}
514
-
515
-	/**
516
-	 * Test isEnabledApps() with cache, not re-reading the list of
517
-	 * enabled apps more than once when a user is set.
518
-	 */
519
-	public function testEnabledAppsCache(): void {
520
-		$userManager = \OCP\Server::get(IUserManager::class);
521
-		$user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
522
-
523
-		\OC_User::setUserId(self::TEST_USER1);
524
-
525
-		$this->setupAppConfigMock()->expects($this->once())
526
-			->method('searchValues')
527
-			->willReturn(
528
-				[
529
-					'app3' => 'yes',
530
-					'app2' => 'no',
531
-				]
532
-			);
533
-
534
-		$apps = \OC_App::getEnabledApps();
535
-		$this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
536
-
537
-		// mock should not be called again here
538
-		$apps = \OC_App::getEnabledApps();
539
-		$this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
540
-
541
-		$this->restoreAppConfig();
542
-		\OC_User::setUserId(null);
543
-
544
-		$user1->delete();
545
-	}
546
-
547
-
548
-	private function setupAppConfigMock() {
549
-		/** @var AppConfig|MockObject */
550
-		$appConfig = $this->getMockBuilder(AppConfig::class)
551
-			->onlyMethods(['searchValues'])
552
-			->setConstructorArgs([\OCP\Server::get(IDBConnection::class)])
553
-			->disableOriginalConstructor()
554
-			->getMock();
555
-
556
-		$this->registerAppConfig($appConfig);
557
-		return $appConfig;
558
-	}
559
-
560
-	/**
561
-	 * Register an app config mock for testing purposes.
562
-	 *
563
-	 * @param IAppConfig $appConfig app config mock
564
-	 */
565
-	private function registerAppConfig(AppConfig $appConfig) {
566
-		$this->overwriteService(AppConfig::class, $appConfig);
567
-		$this->overwriteService(AppManager::class, new AppManager(
568
-			\OCP\Server::get(IUserSession::class),
569
-			\OCP\Server::get(IConfig::class),
570
-			\OCP\Server::get(IGroupManager::class),
571
-			\OCP\Server::get(ICacheFactory::class),
572
-			\OCP\Server::get(IEventDispatcher::class),
573
-			\OCP\Server::get(LoggerInterface::class),
574
-			\OCP\Server::get(ServerVersion::class),
575
-		));
576
-	}
577
-
578
-	/**
579
-	 * Restore the original app config service.
580
-	 */
581
-	private function restoreAppConfig() {
582
-		$this->restoreService(AppConfig::class);
583
-		$this->restoreService(AppManager::class);
584
-
585
-		// Remove the cache of the mocked apps list with a forceRefresh
586
-		\OC_App::getEnabledApps();
587
-	}
588
-
589
-	/**
590
-	 * Providers for the app data values
591
-	 */
592
-	public static function appDataProvider(): array {
593
-		return [
594
-			[
595
-				['description' => " \t  This is a multiline \n test with \n \t \n \n some new lines   "],
596
-				['description' => "This is a multiline \n test with \n \t \n \n some new lines"],
597
-			],
598
-			[
599
-				['description' => " \t  This is a multiline \n test with \n \t   some new lines   "],
600
-				['description' => "This is a multiline \n test with \n \t   some new lines"],
601
-			],
602
-			[
603
-				['description' => hex2bin('5065726d657420646520732761757468656e7469666965722064616e732070697769676f20646972656374656d656e74206176656320736573206964656e74696669616e7473206f776e636c6f75642073616e73206c65732072657461706572206574206d657420c3a0206a6f757273206365757820636920656e20636173206465206368616e67656d656e74206465206d6f742064652070617373652e0d0a0d')],
604
-				['description' => "Permet de s'authentifier dans piwigo directement avec ses identifiants owncloud sans les retaper et met à jours ceux ci en cas de changement de mot de passe."],
605
-			],
606
-			[
607
-				['not-a-description' => " \t  This is a multiline \n test with \n \t   some new lines   "],
608
-				[
609
-					'not-a-description' => " \t  This is a multiline \n test with \n \t   some new lines   ",
610
-					'description' => '',
611
-				],
612
-			],
613
-			[
614
-				['description' => [100, 'bla']],
615
-				['description' => ''],
616
-			],
617
-		];
618
-	}
619
-
620
-	/**
621
-	 * Test app info parser
622
-	 *
623
-	 * @dataProvider appDataProvider
624
-	 * @param array $data
625
-	 * @param array $expected
626
-	 */
627
-	public function testParseAppInfo(array $data, array $expected): void {
628
-		$this->assertSame($expected, \OC_App::parseAppInfo($data));
629
-	}
630
-
631
-	public function testParseAppInfoL10N(): void {
632
-		$parser = new InfoParser();
633
-		$data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-multi-lang.xml');
634
-		$this->assertEquals('English', \OC_App::parseAppInfo($data, 'en')['description']);
635
-		$this->assertEquals('German', \OC_App::parseAppInfo($data, 'de')['description']);
636
-	}
637
-
638
-	public function testParseAppInfoL10NSingleLanguage(): void {
639
-		$parser = new InfoParser();
640
-		$data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-single-lang.xml');
641
-		$this->assertEquals('English', \OC_App::parseAppInfo($data, 'en')['description']);
642
-	}
31
+    public const TEST_USER1 = 'user1';
32
+    public const TEST_USER2 = 'user2';
33
+    public const TEST_USER3 = 'user3';
34
+    public const TEST_GROUP1 = 'group1';
35
+    public const TEST_GROUP2 = 'group2';
36
+
37
+    public static function appVersionsProvider(): array {
38
+        return [
39
+            // exact match
40
+            [
41
+                '6.0.0.0',
42
+                [
43
+                    'requiremin' => '6.0',
44
+                    'requiremax' => '6.0',
45
+                ],
46
+                true
47
+            ],
48
+            // in-between match
49
+            [
50
+                '6.0.0.0',
51
+                [
52
+                    'requiremin' => '5.0',
53
+                    'requiremax' => '7.0',
54
+                ],
55
+                true
56
+            ],
57
+            // app too old
58
+            [
59
+                '6.0.0.0',
60
+                [
61
+                    'requiremin' => '5.0',
62
+                    'requiremax' => '5.0',
63
+                ],
64
+                false
65
+            ],
66
+            // app too new
67
+            [
68
+                '5.0.0.0',
69
+                [
70
+                    'requiremin' => '6.0',
71
+                    'requiremax' => '6.0',
72
+                ],
73
+                false
74
+            ],
75
+            // only min specified
76
+            [
77
+                '6.0.0.0',
78
+                [
79
+                    'requiremin' => '6.0',
80
+                ],
81
+                true
82
+            ],
83
+            // only min specified fail
84
+            [
85
+                '5.0.0.0',
86
+                [
87
+                    'requiremin' => '6.0',
88
+                ],
89
+                false
90
+            ],
91
+            // only min specified legacy
92
+            [
93
+                '6.0.0.0',
94
+                [
95
+                    'require' => '6.0',
96
+                ],
97
+                true
98
+            ],
99
+            // only min specified legacy fail
100
+            [
101
+                '4.0.0.0',
102
+                [
103
+                    'require' => '6.0',
104
+                ],
105
+                false
106
+            ],
107
+            // only max specified
108
+            [
109
+                '5.0.0.0',
110
+                [
111
+                    'requiremax' => '6.0',
112
+                ],
113
+                true
114
+            ],
115
+            // only max specified fail
116
+            [
117
+                '7.0.0.0',
118
+                [
119
+                    'requiremax' => '6.0',
120
+                ],
121
+                false
122
+            ],
123
+            // variations of versions
124
+            // single OC number
125
+            [
126
+                '4',
127
+                [
128
+                    'require' => '4.0',
129
+                ],
130
+                true
131
+            ],
132
+            // multiple OC number
133
+            [
134
+                '4.3.1',
135
+                [
136
+                    'require' => '4.3',
137
+                ],
138
+                true
139
+            ],
140
+            // single app number
141
+            [
142
+                '4',
143
+                [
144
+                    'require' => '4',
145
+                ],
146
+                true
147
+            ],
148
+            // single app number fail
149
+            [
150
+                '4.3',
151
+                [
152
+                    'require' => '5',
153
+                ],
154
+                false
155
+            ],
156
+            // complex
157
+            [
158
+                '5.0.0',
159
+                [
160
+                    'require' => '4.5.1',
161
+                ],
162
+                true
163
+            ],
164
+            // complex fail
165
+            [
166
+                '4.3.1',
167
+                [
168
+                    'require' => '4.3.2',
169
+                ],
170
+                false
171
+            ],
172
+            // two numbers
173
+            [
174
+                '4.3.1',
175
+                [
176
+                    'require' => '4.4',
177
+                ],
178
+                false
179
+            ],
180
+            // one number fail
181
+            [
182
+                '4.3.1',
183
+                [
184
+                    'require' => '5',
185
+                ],
186
+                false
187
+            ],
188
+            // pre-alpha app
189
+            [
190
+                '5.0.3',
191
+                [
192
+                    'require' => '4.93',
193
+                ],
194
+                true
195
+            ],
196
+            // pre-alpha OC
197
+            [
198
+                '6.90.0.2',
199
+                [
200
+                    'require' => '6.90',
201
+                ],
202
+                true
203
+            ],
204
+            // pre-alpha OC max
205
+            [
206
+                '6.90.0.2',
207
+                [
208
+                    'requiremax' => '7',
209
+                ],
210
+                true
211
+            ],
212
+            // expect same major number match
213
+            [
214
+                '5.0.3',
215
+                [
216
+                    'require' => '5',
217
+                ],
218
+                true
219
+            ],
220
+            // expect same major number match
221
+            [
222
+                '5.0.3',
223
+                [
224
+                    'requiremax' => '5',
225
+                ],
226
+                true
227
+            ],
228
+            // dependencies versions before require*
229
+            [
230
+                '6.0.0.0',
231
+                [
232
+                    'requiremin' => '5.0',
233
+                    'requiremax' => '7.0',
234
+                    'dependencies' => [
235
+                        'owncloud' => [
236
+                            '@attributes' => [
237
+                                'min-version' => '7.0',
238
+                                'max-version' => '7.0',
239
+                            ],
240
+                        ],
241
+                    ],
242
+                ],
243
+                false
244
+            ],
245
+            [
246
+                '6.0.0.0',
247
+                [
248
+                    'requiremin' => '5.0',
249
+                    'requiremax' => '7.0',
250
+                    'dependencies' => [
251
+                        'owncloud' => [
252
+                            '@attributes' => [
253
+                                'min-version' => '5.0',
254
+                                'max-version' => '5.0',
255
+                            ],
256
+                        ],
257
+                    ],
258
+                ],
259
+                false
260
+            ],
261
+            [
262
+                '6.0.0.0',
263
+                [
264
+                    'requiremin' => '5.0',
265
+                    'requiremax' => '5.0',
266
+                    'dependencies' => [
267
+                        'owncloud' => [
268
+                            '@attributes' => [
269
+                                'min-version' => '5.0',
270
+                                'max-version' => '7.0',
271
+                            ],
272
+                        ],
273
+                    ],
274
+                ],
275
+                true
276
+            ],
277
+            [
278
+                '9.2.0.0',
279
+                [
280
+                    'dependencies' => [
281
+                        'owncloud' => [
282
+                            '@attributes' => [
283
+                                'min-version' => '9.0',
284
+                                'max-version' => '9.1',
285
+                            ],
286
+                        ],
287
+                        'nextcloud' => [
288
+                            '@attributes' => [
289
+                                'min-version' => '9.1',
290
+                                'max-version' => '9.2',
291
+                            ],
292
+                        ],
293
+                    ],
294
+                ],
295
+                true
296
+            ],
297
+            [
298
+                '9.2.0.0',
299
+                [
300
+                    'dependencies' => [
301
+                        'nextcloud' => [
302
+                            '@attributes' => [
303
+                                'min-version' => '9.1',
304
+                                'max-version' => '9.2',
305
+                            ],
306
+                        ],
307
+                    ],
308
+                ],
309
+                true
310
+            ],
311
+        ];
312
+    }
313
+
314
+    /**
315
+     * @dataProvider appVersionsProvider
316
+     */
317
+    public function testIsAppCompatible($ocVersion, $appInfo, $expectedResult): void {
318
+        $this->assertEquals($expectedResult, \OC_App::isAppCompatible($ocVersion, $appInfo));
319
+    }
320
+
321
+    /**
322
+     * Tests that the app order is correct
323
+     */
324
+    public function testGetEnabledAppsIsSorted(): void {
325
+        $apps = \OC_App::getEnabledApps();
326
+        // copy array
327
+        $sortedApps = $apps;
328
+        sort($sortedApps);
329
+        // 'files' is always on top
330
+        unset($sortedApps[array_search('files', $sortedApps)]);
331
+        array_unshift($sortedApps, 'files');
332
+        $this->assertEquals($sortedApps, $apps);
333
+    }
334
+
335
+    /**
336
+     * Providers for the app config values
337
+     */
338
+    public static function appConfigValuesProvider(): array {
339
+        return [
340
+            // logged in user1
341
+            [
342
+                self::TEST_USER1,
343
+                [
344
+                    'files',
345
+                    'app1',
346
+                    'app3',
347
+                    'appforgroup1',
348
+                    'appforgroup12',
349
+                    'cloud_federation_api',
350
+                    'dav',
351
+                    'federatedfilesharing',
352
+                    'lookup_server_connector',
353
+                    'oauth2',
354
+                    'profile',
355
+                    'provisioning_api',
356
+                    'settings',
357
+                    'theming',
358
+                    'twofactor_backupcodes',
359
+                    'viewer',
360
+                    'workflowengine',
361
+                ],
362
+                false
363
+            ],
364
+            // logged in user2
365
+            [
366
+                self::TEST_USER2,
367
+                [
368
+                    'files',
369
+                    'app1',
370
+                    'app3',
371
+                    'appforgroup12',
372
+                    'appforgroup2',
373
+                    'cloud_federation_api',
374
+                    'dav',
375
+                    'federatedfilesharing',
376
+                    'lookup_server_connector',
377
+                    'oauth2',
378
+                    'profile',
379
+                    'provisioning_api',
380
+                    'settings',
381
+                    'theming',
382
+                    'twofactor_backupcodes',
383
+                    'viewer',
384
+                    'workflowengine',
385
+                ],
386
+                false
387
+            ],
388
+            // logged in user3
389
+            [
390
+                self::TEST_USER3,
391
+                [
392
+                    'files',
393
+                    'app1',
394
+                    'app3',
395
+                    'appforgroup1',
396
+                    'appforgroup12',
397
+                    'appforgroup2',
398
+                    'cloud_federation_api',
399
+                    'dav',
400
+                    'federatedfilesharing',
401
+                    'lookup_server_connector',
402
+                    'oauth2',
403
+                    'profile',
404
+                    'provisioning_api',
405
+                    'settings',
406
+                    'theming',
407
+                    'twofactor_backupcodes',
408
+                    'viewer',
409
+                    'workflowengine',
410
+                ],
411
+                false
412
+            ],
413
+            //  no user, returns all apps
414
+            [
415
+                null,
416
+                [
417
+                    'files',
418
+                    'app1',
419
+                    'app3',
420
+                    'appforgroup1',
421
+                    'appforgroup12',
422
+                    'appforgroup2',
423
+                    'cloud_federation_api',
424
+                    'dav',
425
+                    'federatedfilesharing',
426
+                    'lookup_server_connector',
427
+                    'oauth2',
428
+                    'profile',
429
+                    'provisioning_api',
430
+                    'settings',
431
+                    'theming',
432
+                    'twofactor_backupcodes',
433
+                    'viewer',
434
+                    'workflowengine',
435
+                ],
436
+                false,
437
+            ],
438
+            //  user given, but ask for all
439
+            [
440
+                self::TEST_USER1,
441
+                [
442
+                    'files',
443
+                    'app1',
444
+                    'app3',
445
+                    'appforgroup1',
446
+                    'appforgroup12',
447
+                    'appforgroup2',
448
+                    'cloud_federation_api',
449
+                    'dav',
450
+                    'federatedfilesharing',
451
+                    'lookup_server_connector',
452
+                    'oauth2',
453
+                    'profile',
454
+                    'provisioning_api',
455
+                    'settings',
456
+                    'theming',
457
+                    'twofactor_backupcodes',
458
+                    'viewer',
459
+                    'workflowengine',
460
+                ],
461
+                true,
462
+            ],
463
+        ];
464
+    }
465
+
466
+    /**
467
+     * Test enabled apps
468
+     *
469
+     * @dataProvider appConfigValuesProvider
470
+     */
471
+    public function testEnabledApps($user, $expectedApps, $forceAll): void {
472
+        $userManager = \OCP\Server::get(IUserManager::class);
473
+        $groupManager = \OCP\Server::get(IGroupManager::class);
474
+        $user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
475
+        $user2 = $userManager->createUser(self::TEST_USER2, 'NotAnEasyPassword123456_');
476
+        $user3 = $userManager->createUser(self::TEST_USER3, 'NotAnEasyPassword123456?');
477
+
478
+        $group1 = $groupManager->createGroup(self::TEST_GROUP1);
479
+        $group1->addUser($user1);
480
+        $group1->addUser($user3);
481
+        $group2 = $groupManager->createGroup(self::TEST_GROUP2);
482
+        $group2->addUser($user2);
483
+        $group2->addUser($user3);
484
+
485
+        \OC_User::setUserId($user);
486
+
487
+        $this->setupAppConfigMock()->expects($this->once())
488
+            ->method('searchValues')
489
+            ->willReturn(
490
+                [
491
+                    'app3' => 'yes',
492
+                    'app2' => 'no',
493
+                    'app1' => 'yes',
494
+                    'appforgroup1' => '["group1"]',
495
+                    'appforgroup2' => '["group2"]',
496
+                    'appforgroup12' => '["group2","group1"]',
497
+                ]
498
+            );
499
+
500
+        $apps = \OC_App::getEnabledApps(false, $forceAll);
501
+
502
+        $this->restoreAppConfig();
503
+        \OC_User::setUserId(null);
504
+
505
+        $user1->delete();
506
+        $user2->delete();
507
+        $user3->delete();
508
+
509
+        $group1->delete();
510
+        $group2->delete();
511
+
512
+        $this->assertEquals($expectedApps, $apps);
513
+    }
514
+
515
+    /**
516
+     * Test isEnabledApps() with cache, not re-reading the list of
517
+     * enabled apps more than once when a user is set.
518
+     */
519
+    public function testEnabledAppsCache(): void {
520
+        $userManager = \OCP\Server::get(IUserManager::class);
521
+        $user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
522
+
523
+        \OC_User::setUserId(self::TEST_USER1);
524
+
525
+        $this->setupAppConfigMock()->expects($this->once())
526
+            ->method('searchValues')
527
+            ->willReturn(
528
+                [
529
+                    'app3' => 'yes',
530
+                    'app2' => 'no',
531
+                ]
532
+            );
533
+
534
+        $apps = \OC_App::getEnabledApps();
535
+        $this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
536
+
537
+        // mock should not be called again here
538
+        $apps = \OC_App::getEnabledApps();
539
+        $this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
540
+
541
+        $this->restoreAppConfig();
542
+        \OC_User::setUserId(null);
543
+
544
+        $user1->delete();
545
+    }
546
+
547
+
548
+    private function setupAppConfigMock() {
549
+        /** @var AppConfig|MockObject */
550
+        $appConfig = $this->getMockBuilder(AppConfig::class)
551
+            ->onlyMethods(['searchValues'])
552
+            ->setConstructorArgs([\OCP\Server::get(IDBConnection::class)])
553
+            ->disableOriginalConstructor()
554
+            ->getMock();
555
+
556
+        $this->registerAppConfig($appConfig);
557
+        return $appConfig;
558
+    }
559
+
560
+    /**
561
+     * Register an app config mock for testing purposes.
562
+     *
563
+     * @param IAppConfig $appConfig app config mock
564
+     */
565
+    private function registerAppConfig(AppConfig $appConfig) {
566
+        $this->overwriteService(AppConfig::class, $appConfig);
567
+        $this->overwriteService(AppManager::class, new AppManager(
568
+            \OCP\Server::get(IUserSession::class),
569
+            \OCP\Server::get(IConfig::class),
570
+            \OCP\Server::get(IGroupManager::class),
571
+            \OCP\Server::get(ICacheFactory::class),
572
+            \OCP\Server::get(IEventDispatcher::class),
573
+            \OCP\Server::get(LoggerInterface::class),
574
+            \OCP\Server::get(ServerVersion::class),
575
+        ));
576
+    }
577
+
578
+    /**
579
+     * Restore the original app config service.
580
+     */
581
+    private function restoreAppConfig() {
582
+        $this->restoreService(AppConfig::class);
583
+        $this->restoreService(AppManager::class);
584
+
585
+        // Remove the cache of the mocked apps list with a forceRefresh
586
+        \OC_App::getEnabledApps();
587
+    }
588
+
589
+    /**
590
+     * Providers for the app data values
591
+     */
592
+    public static function appDataProvider(): array {
593
+        return [
594
+            [
595
+                ['description' => " \t  This is a multiline \n test with \n \t \n \n some new lines   "],
596
+                ['description' => "This is a multiline \n test with \n \t \n \n some new lines"],
597
+            ],
598
+            [
599
+                ['description' => " \t  This is a multiline \n test with \n \t   some new lines   "],
600
+                ['description' => "This is a multiline \n test with \n \t   some new lines"],
601
+            ],
602
+            [
603
+                ['description' => hex2bin('5065726d657420646520732761757468656e7469666965722064616e732070697769676f20646972656374656d656e74206176656320736573206964656e74696669616e7473206f776e636c6f75642073616e73206c65732072657461706572206574206d657420c3a0206a6f757273206365757820636920656e20636173206465206368616e67656d656e74206465206d6f742064652070617373652e0d0a0d')],
604
+                ['description' => "Permet de s'authentifier dans piwigo directement avec ses identifiants owncloud sans les retaper et met à jours ceux ci en cas de changement de mot de passe."],
605
+            ],
606
+            [
607
+                ['not-a-description' => " \t  This is a multiline \n test with \n \t   some new lines   "],
608
+                [
609
+                    'not-a-description' => " \t  This is a multiline \n test with \n \t   some new lines   ",
610
+                    'description' => '',
611
+                ],
612
+            ],
613
+            [
614
+                ['description' => [100, 'bla']],
615
+                ['description' => ''],
616
+            ],
617
+        ];
618
+    }
619
+
620
+    /**
621
+     * Test app info parser
622
+     *
623
+     * @dataProvider appDataProvider
624
+     * @param array $data
625
+     * @param array $expected
626
+     */
627
+    public function testParseAppInfo(array $data, array $expected): void {
628
+        $this->assertSame($expected, \OC_App::parseAppInfo($data));
629
+    }
630
+
631
+    public function testParseAppInfoL10N(): void {
632
+        $parser = new InfoParser();
633
+        $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-multi-lang.xml');
634
+        $this->assertEquals('English', \OC_App::parseAppInfo($data, 'en')['description']);
635
+        $this->assertEquals('German', \OC_App::parseAppInfo($data, 'de')['description']);
636
+    }
637
+
638
+    public function testParseAppInfoL10NSingleLanguage(): void {
639
+        $parser = new InfoParser();
640
+        $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-single-lang.xml');
641
+        $this->assertEquals('English', \OC_App::parseAppInfo($data, 'en')['description']);
642
+    }
643 643
 }
Please login to merge, or discard this patch.
tests/lib/AppConfigTest.php 1 patch
Indentation   +1451 added lines, -1451 removed lines patch added patch discarded remove patch
@@ -24,1455 +24,1455 @@
 block discarded – undo
24 24
  * @package Test
25 25
  */
26 26
 class AppConfigTest extends TestCase {
27
-	protected IAppConfig $appConfig;
28
-	protected IDBConnection $connection;
29
-	private LoggerInterface $logger;
30
-	private ICrypto $crypto;
31
-
32
-	private array $originalConfig;
33
-
34
-	/**
35
-	 * @var array<string, array<string, array<string, string, int, bool, bool>>>
36
-	 *                                                                           [appId => [configKey, configValue, valueType, lazy, sensitive]]
37
-	 */
38
-	private static array $baseStruct =
39
-		[
40
-			'testapp' => [
41
-				'enabled' => ['enabled', 'yes'],
42
-				'installed_version' => ['installed_version', '1.2.3'],
43
-				'depends_on' => ['depends_on', 'someapp'],
44
-				'deletethis' => ['deletethis', 'deletethis'],
45
-				'key' => ['key', 'value']
46
-			],
47
-			'someapp' => [
48
-				'key' => ['key', 'value'],
49
-				'otherkey' => ['otherkey', 'othervalue']
50
-			],
51
-			'123456' => [
52
-				'enabled' => ['enabled', 'yes'],
53
-				'key' => ['key', 'value']
54
-			],
55
-			'anotherapp' => [
56
-				'enabled' => ['enabled', 'no'],
57
-				'installed_version' => ['installed_version', '3.2.1'],
58
-				'key' => ['key', 'value']
59
-			],
60
-			'non-sensitive-app' => [
61
-				'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, false],
62
-				'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, false],
63
-			],
64
-			'sensitive-app' => [
65
-				'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, true],
66
-				'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, true],
67
-			],
68
-			'only-lazy' => [
69
-				'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true]
70
-			],
71
-			'typed' => [
72
-				'mixed' => ['mixed', 'mix', IAppConfig::VALUE_MIXED],
73
-				'string' => ['string', 'value', IAppConfig::VALUE_STRING],
74
-				'int' => ['int', '42', IAppConfig::VALUE_INT],
75
-				'float' => ['float', '3.14', IAppConfig::VALUE_FLOAT],
76
-				'bool' => ['bool', '1', IAppConfig::VALUE_BOOL],
77
-				'array' => ['array', '{"test": 1}', IAppConfig::VALUE_ARRAY],
78
-			],
79
-			'prefix-app' => [
80
-				'key1' => ['key1', 'value'],
81
-				'prefix1' => ['prefix1', 'value'],
82
-				'prefix-2' => ['prefix-2', 'value'],
83
-				'key-2' => ['key-2', 'value'],
84
-			]
85
-		];
86
-
87
-	protected function setUp(): void {
88
-		parent::setUp();
89
-
90
-		$this->connection = \OCP\Server::get(IDBConnection::class);
91
-		$this->logger = \OCP\Server::get(LoggerInterface::class);
92
-		$this->crypto = \OCP\Server::get(ICrypto::class);
93
-
94
-		// storing current config and emptying the data table
95
-		$sql = $this->connection->getQueryBuilder();
96
-		$sql->select('*')
97
-			->from('appconfig');
98
-		$result = $sql->executeQuery();
99
-		$this->originalConfig = $result->fetchAll();
100
-		$result->closeCursor();
101
-
102
-		$sql = $this->connection->getQueryBuilder();
103
-		$sql->delete('appconfig');
104
-		$sql->executeStatement();
105
-
106
-		$sql = $this->connection->getQueryBuilder();
107
-		$sql->insert('appconfig')
108
-			->values(
109
-				[
110
-					'appid' => $sql->createParameter('appid'),
111
-					'configkey' => $sql->createParameter('configkey'),
112
-					'configvalue' => $sql->createParameter('configvalue'),
113
-					'type' => $sql->createParameter('type'),
114
-					'lazy' => $sql->createParameter('lazy')
115
-				]
116
-			);
117
-
118
-		foreach (self::$baseStruct as $appId => $appData) {
119
-			foreach ($appData as $key => $row) {
120
-				$value = $row[1];
121
-				$type = $row[2] ?? IAppConfig::VALUE_MIXED;
122
-				if (($row[4] ?? false) === true) {
123
-					$type |= IAppConfig::VALUE_SENSITIVE;
124
-					$value = self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX') . $this->crypto->encrypt($value);
125
-					self::$baseStruct[$appId][$key]['encrypted'] = $value;
126
-				}
127
-
128
-				$sql->setParameters(
129
-					[
130
-						'appid' => $appId,
131
-						'configkey' => $row[0],
132
-						'configvalue' => $value,
133
-						'type' => $type,
134
-						'lazy' => (($row[3] ?? false) === true) ? 1 : 0
135
-					]
136
-				)->executeStatement();
137
-			}
138
-		}
139
-	}
140
-
141
-	protected function tearDown(): void {
142
-		$sql = $this->connection->getQueryBuilder();
143
-		$sql->delete('appconfig');
144
-		$sql->executeStatement();
145
-
146
-		$sql = $this->connection->getQueryBuilder();
147
-		$sql->insert('appconfig')
148
-			->values(
149
-				[
150
-					'appid' => $sql->createParameter('appid'),
151
-					'configkey' => $sql->createParameter('configkey'),
152
-					'configvalue' => $sql->createParameter('configvalue'),
153
-					'lazy' => $sql->createParameter('lazy'),
154
-					'type' => $sql->createParameter('type'),
155
-				]
156
-			);
157
-
158
-		foreach ($this->originalConfig as $key => $configs) {
159
-			$sql->setParameter('appid', $configs['appid'])
160
-				->setParameter('configkey', $configs['configkey'])
161
-				->setParameter('configvalue', $configs['configvalue'])
162
-				->setParameter('lazy', ($configs['lazy'] === '1') ? '1' : '0')
163
-				->setParameter('type', $configs['type']);
164
-			$sql->executeStatement();
165
-		}
166
-
167
-		//		$this->restoreService(AppConfig::class);
168
-		parent::tearDown();
169
-	}
170
-
171
-	/**
172
-	 * @param bool $preLoading TRUE will preload the 'fast' cache, which is the normal behavior of usual
173
-	 *                         IAppConfig
174
-	 *
175
-	 * @return IAppConfig
176
-	 */
177
-	private function generateAppConfig(bool $preLoading = true): IAppConfig {
178
-		/** @var AppConfig $config */
179
-		$config = new \OC\AppConfig(
180
-			$this->connection,
181
-			$this->logger,
182
-			$this->crypto,
183
-		);
184
-		$msg = ' generateAppConfig() failed to confirm cache status';
185
-
186
-		// confirm cache status
187
-		$status = $config->statusCache();
188
-		$this->assertSame(false, $status['fastLoaded'], $msg);
189
-		$this->assertSame(false, $status['lazyLoaded'], $msg);
190
-		$this->assertSame([], $status['fastCache'], $msg);
191
-		$this->assertSame([], $status['lazyCache'], $msg);
192
-		if ($preLoading) {
193
-			// simple way to initiate the load of non-lazy config values in cache
194
-			$config->getValueString('core', 'preload', '');
195
-
196
-			// confirm cache status
197
-			$status = $config->statusCache();
198
-			$this->assertSame(true, $status['fastLoaded'], $msg);
199
-			$this->assertSame(false, $status['lazyLoaded'], $msg);
200
-
201
-			$apps = array_values(array_diff(array_keys(self::$baseStruct), ['only-lazy']));
202
-			$this->assertEqualsCanonicalizing($apps, array_keys($status['fastCache']), $msg);
203
-			$this->assertSame([], array_keys($status['lazyCache']), $msg);
204
-		}
205
-
206
-		return $config;
207
-	}
208
-
209
-	public function testGetApps(): void {
210
-		$config = $this->generateAppConfig(false);
211
-
212
-		$this->assertEqualsCanonicalizing(array_keys(self::$baseStruct), $config->getApps());
213
-	}
214
-
215
-	public function testGetAppInstalledVersions(): void {
216
-		$config = $this->generateAppConfig(false);
217
-
218
-		$this->assertEquals(
219
-			['testapp' => '1.2.3', 'anotherapp' => '3.2.1'],
220
-			$config->getAppInstalledVersions(false)
221
-		);
222
-		$this->assertEquals(
223
-			['testapp' => '1.2.3'],
224
-			$config->getAppInstalledVersions(true)
225
-		);
226
-	}
227
-
228
-	/**
229
-	 * returns list of app and their keys
230
-	 *
231
-	 * @return array<string, string[]> ['appId' => ['key1', 'key2', ]]
232
-	 * @see testGetKeys
233
-	 */
234
-	public static function providerGetAppKeys(): array {
235
-		$appKeys = [];
236
-		foreach (self::$baseStruct as $appId => $appData) {
237
-			$keys = [];
238
-			foreach ($appData as $row) {
239
-				$keys[] = $row[0];
240
-			}
241
-			$appKeys[] = [(string)$appId, $keys];
242
-		}
243
-
244
-		return $appKeys;
245
-	}
246
-
247
-	/**
248
-	 * returns list of config keys
249
-	 *
250
-	 * @return array<string, string, string, int, bool, bool> [appId, key, value, type, lazy, sensitive]
251
-	 * @see testIsSensitive
252
-	 * @see testIsLazy
253
-	 * @see testGetKeys
254
-	 */
255
-	public static function providerGetKeys(): array {
256
-		$appKeys = [];
257
-		foreach (self::$baseStruct as $appId => $appData) {
258
-			foreach ($appData as $row) {
259
-				$appKeys[] = [
260
-					(string)$appId, $row[0], $row[1], $row[2] ?? IAppConfig::VALUE_MIXED, $row[3] ?? false,
261
-					$row[4] ?? false
262
-				];
263
-			}
264
-		}
265
-
266
-		return $appKeys;
267
-	}
268
-
269
-	/**
270
-	 * @dataProvider providerGetAppKeys
271
-	 *
272
-	 * @param string $appId
273
-	 * @param array $expectedKeys
274
-	 */
275
-	public function testGetKeys(string $appId, array $expectedKeys): void {
276
-		$config = $this->generateAppConfig();
277
-		$this->assertEqualsCanonicalizing($expectedKeys, $config->getKeys($appId));
278
-	}
279
-
280
-	public function testGetKeysOnUnknownAppShouldReturnsEmptyArray(): void {
281
-		$config = $this->generateAppConfig();
282
-		$this->assertEqualsCanonicalizing([], $config->getKeys('unknown-app'));
283
-	}
284
-
285
-	/**
286
-	 * @dataProvider providerGetKeys
287
-	 *
288
-	 * @param string $appId
289
-	 * @param string $configKey
290
-	 * @param string $value
291
-	 * @param bool $lazy
292
-	 */
293
-	public function testHasKey(string $appId, string $configKey, string $value, int $type, bool $lazy): void {
294
-		$config = $this->generateAppConfig();
295
-		$this->assertEquals(true, $config->hasKey($appId, $configKey, $lazy));
296
-	}
297
-
298
-	public function testHasKeyOnNonExistentKeyReturnsFalse(): void {
299
-		$config = $this->generateAppConfig();
300
-		$this->assertEquals(false, $config->hasKey(array_keys(self::$baseStruct)[0], 'inexistant-key'));
301
-	}
302
-
303
-	public function testHasKeyOnUnknownAppReturnsFalse(): void {
304
-		$config = $this->generateAppConfig();
305
-		$this->assertEquals(false, $config->hasKey('inexistant-app', 'inexistant-key'));
306
-	}
307
-
308
-	public function testHasKeyOnMistypedAsLazyReturnsFalse(): void {
309
-		$config = $this->generateAppConfig();
310
-		$this->assertSame(false, $config->hasKey('non-sensitive-app', 'non-lazy-key', true));
311
-	}
312
-
313
-	public function testHasKeyOnMistypeAsNonLazyReturnsFalse(): void {
314
-		$config = $this->generateAppConfig();
315
-		$this->assertSame(false, $config->hasKey('non-sensitive-app', 'lazy-key', false));
316
-	}
317
-
318
-	public function testHasKeyOnMistypeAsNonLazyReturnsTrueWithLazyArgumentIsNull(): void {
319
-		$config = $this->generateAppConfig();
320
-		$this->assertSame(true, $config->hasKey('non-sensitive-app', 'lazy-key', null));
321
-	}
322
-
323
-	/**
324
-	 * @dataProvider providerGetKeys
325
-	 */
326
-	public function testIsSensitive(
327
-		string $appId, string $configKey, string $configValue, int $type, bool $lazy, bool $sensitive,
328
-	): void {
329
-		$config = $this->generateAppConfig();
330
-		$this->assertEquals($sensitive, $config->isSensitive($appId, $configKey, $lazy));
331
-	}
332
-
333
-	public function testIsSensitiveOnNonExistentKeyThrowsException(): void {
334
-		$config = $this->generateAppConfig();
335
-		$this->expectException(AppConfigUnknownKeyException::class);
336
-		$config->isSensitive(array_keys(self::$baseStruct)[0], 'inexistant-key');
337
-	}
338
-
339
-	public function testIsSensitiveOnUnknownAppThrowsException(): void {
340
-		$config = $this->generateAppConfig();
341
-		$this->expectException(AppConfigUnknownKeyException::class);
342
-		$config->isSensitive('unknown-app', 'inexistant-key');
343
-	}
344
-
345
-	public function testIsSensitiveOnSensitiveMistypedAsLazy(): void {
346
-		$config = $this->generateAppConfig();
347
-		$this->assertSame(true, $config->isSensitive('sensitive-app', 'non-lazy-key', true));
348
-	}
349
-
350
-	public function testIsSensitiveOnNonSensitiveMistypedAsLazy(): void {
351
-		$config = $this->generateAppConfig();
352
-		$this->assertSame(false, $config->isSensitive('non-sensitive-app', 'non-lazy-key', true));
353
-	}
354
-
355
-	public function testIsSensitiveOnSensitiveMistypedAsNonLazyThrowsException(): void {
356
-		$config = $this->generateAppConfig();
357
-		$this->expectException(AppConfigUnknownKeyException::class);
358
-		$config->isSensitive('sensitive-app', 'lazy-key', false);
359
-	}
360
-
361
-	public function testIsSensitiveOnNonSensitiveMistypedAsNonLazyThrowsException(): void {
362
-		$config = $this->generateAppConfig();
363
-		$this->expectException(AppConfigUnknownKeyException::class);
364
-		$config->isSensitive('non-sensitive-app', 'lazy-key', false);
365
-	}
366
-
367
-	/**
368
-	 * @dataProvider providerGetKeys
369
-	 */
370
-	public function testIsLazy(string $appId, string $configKey, string $configValue, int $type, bool $lazy,
371
-	): void {
372
-		$config = $this->generateAppConfig();
373
-		$this->assertEquals($lazy, $config->isLazy($appId, $configKey));
374
-	}
375
-
376
-	public function testIsLazyOnNonExistentKeyThrowsException(): void {
377
-		$config = $this->generateAppConfig();
378
-		$this->expectException(AppConfigUnknownKeyException::class);
379
-		$config->isLazy(array_keys(self::$baseStruct)[0], 'inexistant-key');
380
-	}
381
-
382
-	public function testIsLazyOnUnknownAppThrowsException(): void {
383
-		$config = $this->generateAppConfig();
384
-		$this->expectException(AppConfigUnknownKeyException::class);
385
-		$config->isLazy('unknown-app', 'inexistant-key');
386
-	}
387
-
388
-	public function testGetAllValues(): void {
389
-		$config = $this->generateAppConfig();
390
-		$this->assertEquals(
391
-			[
392
-				'array' => ['test' => 1],
393
-				'bool' => true,
394
-				'float' => 3.14,
395
-				'int' => 42,
396
-				'mixed' => 'mix',
397
-				'string' => 'value',
398
-			],
399
-			$config->getAllValues('typed')
400
-		);
401
-	}
402
-
403
-	public function testGetAllValuesWithEmptyApp(): void {
404
-		$config = $this->generateAppConfig();
405
-		$this->expectException(InvalidArgumentException::class);
406
-		$config->getAllValues('');
407
-	}
408
-
409
-	/**
410
-	 * @dataProvider providerGetAppKeys
411
-	 *
412
-	 * @param string $appId
413
-	 * @param array $keys
414
-	 */
415
-	public function testGetAllValuesWithEmptyKey(string $appId, array $keys): void {
416
-		$config = $this->generateAppConfig();
417
-		$this->assertEqualsCanonicalizing($keys, array_keys($config->getAllValues($appId, '')));
418
-	}
419
-
420
-	public function testGetAllValuesWithPrefix(): void {
421
-		$config = $this->generateAppConfig();
422
-		$this->assertEqualsCanonicalizing(['prefix1', 'prefix-2'], array_keys($config->getAllValues('prefix-app', 'prefix')));
423
-	}
424
-
425
-	public function testSearchValues(): void {
426
-		$config = $this->generateAppConfig();
427
-		$this->assertEqualsCanonicalizing(['testapp' => 'yes', '123456' => 'yes', 'anotherapp' => 'no'], $config->searchValues('enabled'));
428
-	}
429
-
430
-	public function testGetValueString(): void {
431
-		$config = $this->generateAppConfig();
432
-		$this->assertSame('value', $config->getValueString('typed', 'string', ''));
433
-	}
434
-
435
-	public function testGetValueStringOnUnknownAppReturnsDefault(): void {
436
-		$config = $this->generateAppConfig();
437
-		$this->assertSame('default-1', $config->getValueString('typed-1', 'string', 'default-1'));
438
-	}
439
-
440
-	public function testGetValueStringOnNonExistentKeyReturnsDefault(): void {
441
-		$config = $this->generateAppConfig();
442
-		$this->assertSame('default-2', $config->getValueString('typed', 'string-2', 'default-2'));
443
-	}
444
-
445
-	public function testGetValueStringOnWrongType(): void {
446
-		$config = $this->generateAppConfig();
447
-		$this->expectException(AppConfigTypeConflictException::class);
448
-		$config->getValueString('typed', 'int');
449
-	}
450
-
451
-	public function testGetNonLazyValueStringAsLazy(): void {
452
-		$config = $this->generateAppConfig();
453
-		$this->assertSame('value', $config->getValueString('non-sensitive-app', 'non-lazy-key', 'default', lazy: true));
454
-	}
455
-
456
-	public function testGetValueInt(): void {
457
-		$config = $this->generateAppConfig();
458
-		$this->assertSame(42, $config->getValueInt('typed', 'int', 0));
459
-	}
460
-
461
-	public function testGetValueIntOnUnknownAppReturnsDefault(): void {
462
-		$config = $this->generateAppConfig();
463
-		$this->assertSame(1, $config->getValueInt('typed-1', 'int', 1));
464
-	}
465
-
466
-	public function testGetValueIntOnNonExistentKeyReturnsDefault(): void {
467
-		$config = $this->generateAppConfig();
468
-		$this->assertSame(2, $config->getValueInt('typed', 'int-2', 2));
469
-	}
470
-
471
-	public function testGetValueIntOnWrongType(): void {
472
-		$config = $this->generateAppConfig();
473
-		$this->expectException(AppConfigTypeConflictException::class);
474
-		$config->getValueInt('typed', 'float');
475
-	}
476
-
477
-	public function testGetValueFloat(): void {
478
-		$config = $this->generateAppConfig();
479
-		$this->assertSame(3.14, $config->getValueFloat('typed', 'float', 0));
480
-	}
481
-
482
-	public function testGetValueFloatOnNonUnknownAppReturnsDefault(): void {
483
-		$config = $this->generateAppConfig();
484
-		$this->assertSame(1.11, $config->getValueFloat('typed-1', 'float', 1.11));
485
-	}
486
-
487
-	public function testGetValueFloatOnNonExistentKeyReturnsDefault(): void {
488
-		$config = $this->generateAppConfig();
489
-		$this->assertSame(2.22, $config->getValueFloat('typed', 'float-2', 2.22));
490
-	}
491
-
492
-	public function testGetValueFloatOnWrongType(): void {
493
-		$config = $this->generateAppConfig();
494
-		$this->expectException(AppConfigTypeConflictException::class);
495
-		$config->getValueFloat('typed', 'bool');
496
-	}
497
-
498
-	public function testGetValueBool(): void {
499
-		$config = $this->generateAppConfig();
500
-		$this->assertSame(true, $config->getValueBool('typed', 'bool'));
501
-	}
502
-
503
-	public function testGetValueBoolOnUnknownAppReturnsDefault(): void {
504
-		$config = $this->generateAppConfig();
505
-		$this->assertSame(false, $config->getValueBool('typed-1', 'bool', false));
506
-	}
507
-
508
-	public function testGetValueBoolOnNonExistentKeyReturnsDefault(): void {
509
-		$config = $this->generateAppConfig();
510
-		$this->assertSame(false, $config->getValueBool('typed', 'bool-2'));
511
-	}
512
-
513
-	public function testGetValueBoolOnWrongType(): void {
514
-		$config = $this->generateAppConfig();
515
-		$this->expectException(AppConfigTypeConflictException::class);
516
-		$config->getValueBool('typed', 'array');
517
-	}
518
-
519
-	public function testGetValueArray(): void {
520
-		$config = $this->generateAppConfig();
521
-		$this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('typed', 'array', []));
522
-	}
523
-
524
-	public function testGetValueArrayOnUnknownAppReturnsDefault(): void {
525
-		$config = $this->generateAppConfig();
526
-		$this->assertSame([1], $config->getValueArray('typed-1', 'array', [1]));
527
-	}
528
-
529
-	public function testGetValueArrayOnNonExistentKeyReturnsDefault(): void {
530
-		$config = $this->generateAppConfig();
531
-		$this->assertSame([1, 2], $config->getValueArray('typed', 'array-2', [1, 2]));
532
-	}
533
-
534
-	public function testGetValueArrayOnWrongType(): void {
535
-		$config = $this->generateAppConfig();
536
-		$this->expectException(AppConfigTypeConflictException::class);
537
-		$config->getValueArray('typed', 'string');
538
-	}
539
-
540
-
541
-	/**
542
-	 * @return array
543
-	 * @see testGetValueType
544
-	 *
545
-	 * @see testGetValueMixed
546
-	 */
547
-	public static function providerGetValueMixed(): array {
548
-		return [
549
-			// key, value, type
550
-			['mixed', 'mix', IAppConfig::VALUE_MIXED],
551
-			['string', 'value', IAppConfig::VALUE_STRING],
552
-			['int', '42', IAppConfig::VALUE_INT],
553
-			['float', '3.14', IAppConfig::VALUE_FLOAT],
554
-			['bool', '1', IAppConfig::VALUE_BOOL],
555
-			['array', '{"test": 1}', IAppConfig::VALUE_ARRAY],
556
-		];
557
-	}
558
-
559
-	/**
560
-	 * @dataProvider providerGetValueMixed
561
-	 *
562
-	 * @param string $key
563
-	 * @param string $value
564
-	 */
565
-	public function testGetValueMixed(string $key, string $value): void {
566
-		$config = $this->generateAppConfig();
567
-		$this->assertSame($value, $config->getValueMixed('typed', $key));
568
-	}
569
-
570
-	/**
571
-	 * @dataProvider providerGetValueMixed
572
-	 *
573
-	 * @param string $key
574
-	 * @param string $value
575
-	 * @param int $type
576
-	 */
577
-	public function testGetValueType(string $key, string $value, int $type): void {
578
-		$config = $this->generateAppConfig();
579
-		$this->assertSame($type, $config->getValueType('typed', $key));
580
-	}
581
-
582
-	public function testGetValueTypeOnUnknownApp(): void {
583
-		$config = $this->generateAppConfig();
584
-		$this->expectException(AppConfigUnknownKeyException::class);
585
-		$config->getValueType('typed-1', 'string');
586
-	}
587
-
588
-	public function testGetValueTypeOnNonExistentKey(): void {
589
-		$config = $this->generateAppConfig();
590
-		$this->expectException(AppConfigUnknownKeyException::class);
591
-		$config->getValueType('typed', 'string-2');
592
-	}
593
-
594
-	public function testSetValueString(): void {
595
-		$config = $this->generateAppConfig();
596
-		$config->setValueString('feed', 'string', 'value-1');
597
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
598
-	}
599
-
600
-	public function testSetValueStringCache(): void {
601
-		$config = $this->generateAppConfig();
602
-		$config->setValueString('feed', 'string', 'value-1');
603
-		$status = $config->statusCache();
604
-		$this->assertSame('value-1', $status['fastCache']['feed']['string']);
605
-	}
606
-
607
-	public function testSetValueStringDatabase(): void {
608
-		$config = $this->generateAppConfig();
609
-		$config->setValueString('feed', 'string', 'value-1');
610
-		$config->clearCache();
611
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
612
-	}
613
-
614
-	public function testSetValueStringIsUpdated(): void {
615
-		$config = $this->generateAppConfig();
616
-		$config->setValueString('feed', 'string', 'value-1');
617
-		$this->assertSame(true, $config->setValueString('feed', 'string', 'value-2'));
618
-	}
619
-
620
-	public function testSetValueStringIsNotUpdated(): void {
621
-		$config = $this->generateAppConfig();
622
-		$config->setValueString('feed', 'string', 'value-1');
623
-		$this->assertSame(false, $config->setValueString('feed', 'string', 'value-1'));
624
-	}
625
-
626
-	public function testSetValueStringIsUpdatedCache(): void {
627
-		$config = $this->generateAppConfig();
628
-		$config->setValueString('feed', 'string', 'value-1');
629
-		$config->setValueString('feed', 'string', 'value-2');
630
-		$status = $config->statusCache();
631
-		$this->assertSame('value-2', $status['fastCache']['feed']['string']);
632
-	}
633
-
634
-	public function testSetValueStringIsUpdatedDatabase(): void {
635
-		$config = $this->generateAppConfig();
636
-		$config->setValueString('feed', 'string', 'value-1');
637
-		$config->setValueString('feed', 'string', 'value-2');
638
-		$config->clearCache();
639
-		$this->assertSame('value-2', $config->getValueString('feed', 'string', ''));
640
-	}
641
-
642
-	public function testSetValueInt(): void {
643
-		$config = $this->generateAppConfig();
644
-		$config->setValueInt('feed', 'int', 42);
645
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
646
-	}
647
-
648
-	public function testSetValueIntCache(): void {
649
-		$config = $this->generateAppConfig();
650
-		$config->setValueInt('feed', 'int', 42);
651
-		$status = $config->statusCache();
652
-		$this->assertSame('42', $status['fastCache']['feed']['int']);
653
-	}
654
-
655
-	public function testSetValueIntDatabase(): void {
656
-		$config = $this->generateAppConfig();
657
-		$config->setValueInt('feed', 'int', 42);
658
-		$config->clearCache();
659
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
660
-	}
661
-
662
-	public function testSetValueIntIsUpdated(): void {
663
-		$config = $this->generateAppConfig();
664
-		$config->setValueInt('feed', 'int', 42);
665
-		$this->assertSame(true, $config->setValueInt('feed', 'int', 17));
666
-	}
667
-
668
-	public function testSetValueIntIsNotUpdated(): void {
669
-		$config = $this->generateAppConfig();
670
-		$config->setValueInt('feed', 'int', 42);
671
-		$this->assertSame(false, $config->setValueInt('feed', 'int', 42));
672
-	}
673
-
674
-	public function testSetValueIntIsUpdatedCache(): void {
675
-		$config = $this->generateAppConfig();
676
-		$config->setValueInt('feed', 'int', 42);
677
-		$config->setValueInt('feed', 'int', 17);
678
-		$status = $config->statusCache();
679
-		$this->assertSame('17', $status['fastCache']['feed']['int']);
680
-	}
681
-
682
-	public function testSetValueIntIsUpdatedDatabase(): void {
683
-		$config = $this->generateAppConfig();
684
-		$config->setValueInt('feed', 'int', 42);
685
-		$config->setValueInt('feed', 'int', 17);
686
-		$config->clearCache();
687
-		$this->assertSame(17, $config->getValueInt('feed', 'int', 0));
688
-	}
689
-
690
-	public function testSetValueFloat(): void {
691
-		$config = $this->generateAppConfig();
692
-		$config->setValueFloat('feed', 'float', 3.14);
693
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
694
-	}
695
-
696
-	public function testSetValueFloatCache(): void {
697
-		$config = $this->generateAppConfig();
698
-		$config->setValueFloat('feed', 'float', 3.14);
699
-		$status = $config->statusCache();
700
-		$this->assertSame('3.14', $status['fastCache']['feed']['float']);
701
-	}
702
-
703
-	public function testSetValueFloatDatabase(): void {
704
-		$config = $this->generateAppConfig();
705
-		$config->setValueFloat('feed', 'float', 3.14);
706
-		$config->clearCache();
707
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
708
-	}
709
-
710
-	public function testSetValueFloatIsUpdated(): void {
711
-		$config = $this->generateAppConfig();
712
-		$config->setValueFloat('feed', 'float', 3.14);
713
-		$this->assertSame(true, $config->setValueFloat('feed', 'float', 1.23));
714
-	}
715
-
716
-	public function testSetValueFloatIsNotUpdated(): void {
717
-		$config = $this->generateAppConfig();
718
-		$config->setValueFloat('feed', 'float', 3.14);
719
-		$this->assertSame(false, $config->setValueFloat('feed', 'float', 3.14));
720
-	}
721
-
722
-	public function testSetValueFloatIsUpdatedCache(): void {
723
-		$config = $this->generateAppConfig();
724
-		$config->setValueFloat('feed', 'float', 3.14);
725
-		$config->setValueFloat('feed', 'float', 1.23);
726
-		$status = $config->statusCache();
727
-		$this->assertSame('1.23', $status['fastCache']['feed']['float']);
728
-	}
729
-
730
-	public function testSetValueFloatIsUpdatedDatabase(): void {
731
-		$config = $this->generateAppConfig();
732
-		$config->setValueFloat('feed', 'float', 3.14);
733
-		$config->setValueFloat('feed', 'float', 1.23);
734
-		$config->clearCache();
735
-		$this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0));
736
-	}
737
-
738
-	public function testSetValueBool(): void {
739
-		$config = $this->generateAppConfig();
740
-		$config->setValueBool('feed', 'bool', true);
741
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false));
742
-	}
743
-
744
-	public function testSetValueBoolCache(): void {
745
-		$config = $this->generateAppConfig();
746
-		$config->setValueBool('feed', 'bool', true);
747
-		$status = $config->statusCache();
748
-		$this->assertSame('1', $status['fastCache']['feed']['bool']);
749
-	}
750
-
751
-	public function testSetValueBoolDatabase(): void {
752
-		$config = $this->generateAppConfig();
753
-		$config->setValueBool('feed', 'bool', true);
754
-		$config->clearCache();
755
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false));
756
-	}
757
-
758
-	public function testSetValueBoolIsUpdated(): void {
759
-		$config = $this->generateAppConfig();
760
-		$config->setValueBool('feed', 'bool', true);
761
-		$this->assertSame(true, $config->setValueBool('feed', 'bool', false));
762
-	}
763
-
764
-	public function testSetValueBoolIsNotUpdated(): void {
765
-		$config = $this->generateAppConfig();
766
-		$config->setValueBool('feed', 'bool', true);
767
-		$this->assertSame(false, $config->setValueBool('feed', 'bool', true));
768
-	}
769
-
770
-	public function testSetValueBoolIsUpdatedCache(): void {
771
-		$config = $this->generateAppConfig();
772
-		$config->setValueBool('feed', 'bool', true);
773
-		$config->setValueBool('feed', 'bool', false);
774
-		$status = $config->statusCache();
775
-		$this->assertSame('0', $status['fastCache']['feed']['bool']);
776
-	}
777
-
778
-	public function testSetValueBoolIsUpdatedDatabase(): void {
779
-		$config = $this->generateAppConfig();
780
-		$config->setValueBool('feed', 'bool', true);
781
-		$config->setValueBool('feed', 'bool', false);
782
-		$config->clearCache();
783
-		$this->assertSame(false, $config->getValueBool('feed', 'bool', true));
784
-	}
785
-
786
-
787
-	public function testSetValueArray(): void {
788
-		$config = $this->generateAppConfig();
789
-		$config->setValueArray('feed', 'array', ['test' => 1]);
790
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
791
-	}
792
-
793
-	public function testSetValueArrayCache(): void {
794
-		$config = $this->generateAppConfig();
795
-		$config->setValueArray('feed', 'array', ['test' => 1]);
796
-		$status = $config->statusCache();
797
-		$this->assertSame('{"test":1}', $status['fastCache']['feed']['array']);
798
-	}
799
-
800
-	public function testSetValueArrayDatabase(): void {
801
-		$config = $this->generateAppConfig();
802
-		$config->setValueArray('feed', 'array', ['test' => 1]);
803
-		$config->clearCache();
804
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
805
-	}
806
-
807
-	public function testSetValueArrayIsUpdated(): void {
808
-		$config = $this->generateAppConfig();
809
-		$config->setValueArray('feed', 'array', ['test' => 1]);
810
-		$this->assertSame(true, $config->setValueArray('feed', 'array', ['test' => 2]));
811
-	}
812
-
813
-	public function testSetValueArrayIsNotUpdated(): void {
814
-		$config = $this->generateAppConfig();
815
-		$config->setValueArray('feed', 'array', ['test' => 1]);
816
-		$this->assertSame(false, $config->setValueArray('feed', 'array', ['test' => 1]));
817
-	}
818
-
819
-	public function testSetValueArrayIsUpdatedCache(): void {
820
-		$config = $this->generateAppConfig();
821
-		$config->setValueArray('feed', 'array', ['test' => 1]);
822
-		$config->setValueArray('feed', 'array', ['test' => 2]);
823
-		$status = $config->statusCache();
824
-		$this->assertSame('{"test":2}', $status['fastCache']['feed']['array']);
825
-	}
826
-
827
-	public function testSetValueArrayIsUpdatedDatabase(): void {
828
-		$config = $this->generateAppConfig();
829
-		$config->setValueArray('feed', 'array', ['test' => 1]);
830
-		$config->setValueArray('feed', 'array', ['test' => 2]);
831
-		$config->clearCache();
832
-		$this->assertSame(['test' => 2], $config->getValueArray('feed', 'array', []));
833
-	}
834
-
835
-	public function testSetLazyValueString(): void {
836
-		$config = $this->generateAppConfig();
837
-		$config->setValueString('feed', 'string', 'value-1', true);
838
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
839
-	}
840
-
841
-	public function testSetLazyValueStringCache(): void {
842
-		$config = $this->generateAppConfig();
843
-		$config->setValueString('feed', 'string', 'value-1', true);
844
-		$status = $config->statusCache();
845
-		$this->assertSame('value-1', $status['lazyCache']['feed']['string']);
846
-	}
847
-
848
-	public function testSetLazyValueStringDatabase(): void {
849
-		$config = $this->generateAppConfig();
850
-		$config->setValueString('feed', 'string', 'value-1', true);
851
-		$config->clearCache();
852
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
853
-	}
854
-
855
-	public function testSetLazyValueStringAsNonLazy(): void {
856
-		$config = $this->generateAppConfig();
857
-		$config->setValueString('feed', 'string', 'value-1', true);
858
-		$config->setValueString('feed', 'string', 'value-1', false);
859
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
860
-	}
861
-
862
-	public function testSetNonLazyValueStringAsLazy(): void {
863
-		$config = $this->generateAppConfig();
864
-		$config->setValueString('feed', 'string', 'value-1', false);
865
-		$config->setValueString('feed', 'string', 'value-1', true);
866
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
867
-	}
868
-
869
-	public function testSetSensitiveValueString(): void {
870
-		$config = $this->generateAppConfig();
871
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
872
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
873
-	}
874
-
875
-	public function testSetSensitiveValueStringCache(): void {
876
-		$config = $this->generateAppConfig();
877
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
878
-		$status = $config->statusCache();
879
-		$this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['string']);
880
-	}
881
-
882
-	public function testSetSensitiveValueStringDatabase(): void {
883
-		$config = $this->generateAppConfig();
884
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
885
-		$config->clearCache();
886
-		$this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
887
-	}
888
-
889
-	public function testSetNonSensitiveValueStringAsSensitive(): void {
890
-		$config = $this->generateAppConfig();
891
-		$config->setValueString('feed', 'string', 'value-1', sensitive: false);
892
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
893
-		$this->assertSame(true, $config->isSensitive('feed', 'string'));
894
-
895
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-1');
896
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-2');
897
-	}
898
-
899
-	public function testSetSensitiveValueStringAsNonSensitiveStaysSensitive(): void {
900
-		$config = $this->generateAppConfig();
901
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
902
-		$config->setValueString('feed', 'string', 'value-2', sensitive: false);
903
-		$this->assertSame(true, $config->isSensitive('feed', 'string'));
904
-
905
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-1');
906
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-2');
907
-	}
908
-
909
-	public function testSetSensitiveValueStringAsNonSensitiveAreStillUpdated(): void {
910
-		$config = $this->generateAppConfig();
911
-		$config->setValueString('feed', 'string', 'value-1', sensitive: true);
912
-		$config->setValueString('feed', 'string', 'value-2', sensitive: false);
913
-		$this->assertSame('value-2', $config->getValueString('feed', 'string', ''));
914
-
915
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-1');
916
-		$this->assertConfigValueNotEquals('feed', 'string', 'value-2');
917
-	}
918
-
919
-	public function testSetLazyValueInt(): void {
920
-		$config = $this->generateAppConfig();
921
-		$config->setValueInt('feed', 'int', 42, true);
922
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
923
-	}
924
-
925
-	public function testSetLazyValueIntCache(): void {
926
-		$config = $this->generateAppConfig();
927
-		$config->setValueInt('feed', 'int', 42, true);
928
-		$status = $config->statusCache();
929
-		$this->assertSame('42', $status['lazyCache']['feed']['int']);
930
-	}
931
-
932
-	public function testSetLazyValueIntDatabase(): void {
933
-		$config = $this->generateAppConfig();
934
-		$config->setValueInt('feed', 'int', 42, true);
935
-		$config->clearCache();
936
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
937
-	}
938
-
939
-	public function testSetLazyValueIntAsNonLazy(): void {
940
-		$config = $this->generateAppConfig();
941
-		$config->setValueInt('feed', 'int', 42, true);
942
-		$config->setValueInt('feed', 'int', 42, false);
943
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
944
-	}
945
-
946
-	public function testSetNonLazyValueIntAsLazy(): void {
947
-		$config = $this->generateAppConfig();
948
-		$config->setValueInt('feed', 'int', 42, false);
949
-		$config->setValueInt('feed', 'int', 42, true);
950
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
951
-	}
952
-
953
-	public function testSetSensitiveValueInt(): void {
954
-		$config = $this->generateAppConfig();
955
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
956
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
957
-	}
958
-
959
-	public function testSetSensitiveValueIntCache(): void {
960
-		$config = $this->generateAppConfig();
961
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
962
-		$status = $config->statusCache();
963
-		$this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['int']);
964
-	}
965
-
966
-	public function testSetSensitiveValueIntDatabase(): void {
967
-		$config = $this->generateAppConfig();
968
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
969
-		$config->clearCache();
970
-		$this->assertSame(42, $config->getValueInt('feed', 'int', 0));
971
-	}
972
-
973
-	public function testSetNonSensitiveValueIntAsSensitive(): void {
974
-		$config = $this->generateAppConfig();
975
-		$config->setValueInt('feed', 'int', 42);
976
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
977
-		$this->assertSame(true, $config->isSensitive('feed', 'int'));
978
-	}
979
-
980
-	public function testSetSensitiveValueIntAsNonSensitiveStaysSensitive(): void {
981
-		$config = $this->generateAppConfig();
982
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
983
-		$config->setValueInt('feed', 'int', 17);
984
-		$this->assertSame(true, $config->isSensitive('feed', 'int'));
985
-	}
986
-
987
-	public function testSetSensitiveValueIntAsNonSensitiveAreStillUpdated(): void {
988
-		$config = $this->generateAppConfig();
989
-		$config->setValueInt('feed', 'int', 42, sensitive: true);
990
-		$config->setValueInt('feed', 'int', 17);
991
-		$this->assertSame(17, $config->getValueInt('feed', 'int', 0));
992
-	}
993
-
994
-	public function testSetLazyValueFloat(): void {
995
-		$config = $this->generateAppConfig();
996
-		$config->setValueFloat('feed', 'float', 3.14, true);
997
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
998
-	}
999
-
1000
-	public function testSetLazyValueFloatCache(): void {
1001
-		$config = $this->generateAppConfig();
1002
-		$config->setValueFloat('feed', 'float', 3.14, true);
1003
-		$status = $config->statusCache();
1004
-		$this->assertSame('3.14', $status['lazyCache']['feed']['float']);
1005
-	}
1006
-
1007
-	public function testSetLazyValueFloatDatabase(): void {
1008
-		$config = $this->generateAppConfig();
1009
-		$config->setValueFloat('feed', 'float', 3.14, true);
1010
-		$config->clearCache();
1011
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1012
-	}
1013
-
1014
-	public function testSetLazyValueFloatAsNonLazy(): void {
1015
-		$config = $this->generateAppConfig();
1016
-		$config->setValueFloat('feed', 'float', 3.14, true);
1017
-		$config->setValueFloat('feed', 'float', 3.14, false);
1018
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1019
-	}
1020
-
1021
-	public function testSetNonLazyValueFloatAsLazy(): void {
1022
-		$config = $this->generateAppConfig();
1023
-		$config->setValueFloat('feed', 'float', 3.14, false);
1024
-		$config->setValueFloat('feed', 'float', 3.14, true);
1025
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1026
-	}
1027
-
1028
-	public function testSetSensitiveValueFloat(): void {
1029
-		$config = $this->generateAppConfig();
1030
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1031
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1032
-	}
1033
-
1034
-	public function testSetSensitiveValueFloatCache(): void {
1035
-		$config = $this->generateAppConfig();
1036
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1037
-		$status = $config->statusCache();
1038
-		$this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['float']);
1039
-	}
1040
-
1041
-	public function testSetSensitiveValueFloatDatabase(): void {
1042
-		$config = $this->generateAppConfig();
1043
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1044
-		$config->clearCache();
1045
-		$this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1046
-	}
1047
-
1048
-	public function testSetNonSensitiveValueFloatAsSensitive(): void {
1049
-		$config = $this->generateAppConfig();
1050
-		$config->setValueFloat('feed', 'float', 3.14);
1051
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1052
-		$this->assertSame(true, $config->isSensitive('feed', 'float'));
1053
-	}
1054
-
1055
-	public function testSetSensitiveValueFloatAsNonSensitiveStaysSensitive(): void {
1056
-		$config = $this->generateAppConfig();
1057
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1058
-		$config->setValueFloat('feed', 'float', 1.23);
1059
-		$this->assertSame(true, $config->isSensitive('feed', 'float'));
1060
-	}
1061
-
1062
-	public function testSetSensitiveValueFloatAsNonSensitiveAreStillUpdated(): void {
1063
-		$config = $this->generateAppConfig();
1064
-		$config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1065
-		$config->setValueFloat('feed', 'float', 1.23);
1066
-		$this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0));
1067
-	}
1068
-
1069
-	public function testSetLazyValueBool(): void {
1070
-		$config = $this->generateAppConfig();
1071
-		$config->setValueBool('feed', 'bool', true, true);
1072
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1073
-	}
1074
-
1075
-	public function testSetLazyValueBoolCache(): void {
1076
-		$config = $this->generateAppConfig();
1077
-		$config->setValueBool('feed', 'bool', true, true);
1078
-		$status = $config->statusCache();
1079
-		$this->assertSame('1', $status['lazyCache']['feed']['bool']);
1080
-	}
1081
-
1082
-	public function testSetLazyValueBoolDatabase(): void {
1083
-		$config = $this->generateAppConfig();
1084
-		$config->setValueBool('feed', 'bool', true, true);
1085
-		$config->clearCache();
1086
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1087
-	}
1088
-
1089
-	public function testSetLazyValueBoolAsNonLazy(): void {
1090
-		$config = $this->generateAppConfig();
1091
-		$config->setValueBool('feed', 'bool', true, true);
1092
-		$config->setValueBool('feed', 'bool', true, false);
1093
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false));
1094
-	}
1095
-
1096
-	public function testSetNonLazyValueBoolAsLazy(): void {
1097
-		$config = $this->generateAppConfig();
1098
-		$config->setValueBool('feed', 'bool', true, false);
1099
-		$config->setValueBool('feed', 'bool', true, true);
1100
-		$this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1101
-	}
1102
-
1103
-	public function testSetLazyValueArray(): void {
1104
-		$config = $this->generateAppConfig();
1105
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1106
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1107
-	}
1108
-
1109
-	public function testSetLazyValueArrayCache(): void {
1110
-		$config = $this->generateAppConfig();
1111
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1112
-		$status = $config->statusCache();
1113
-		$this->assertSame('{"test":1}', $status['lazyCache']['feed']['array']);
1114
-	}
1115
-
1116
-	public function testSetLazyValueArrayDatabase(): void {
1117
-		$config = $this->generateAppConfig();
1118
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1119
-		$config->clearCache();
1120
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1121
-	}
1122
-
1123
-	public function testSetLazyValueArrayAsNonLazy(): void {
1124
-		$config = $this->generateAppConfig();
1125
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1126
-		$config->setValueArray('feed', 'array', ['test' => 1], false);
1127
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
1128
-	}
1129
-
1130
-	public function testSetNonLazyValueArrayAsLazy(): void {
1131
-		$config = $this->generateAppConfig();
1132
-		$config->setValueArray('feed', 'array', ['test' => 1], false);
1133
-		$config->setValueArray('feed', 'array', ['test' => 1], true);
1134
-		$this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1135
-	}
1136
-
1137
-
1138
-	public function testSetSensitiveValueArray(): void {
1139
-		$config = $this->generateAppConfig();
1140
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1141
-		$this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', []));
1142
-	}
1143
-
1144
-	public function testSetSensitiveValueArrayCache(): void {
1145
-		$config = $this->generateAppConfig();
1146
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1147
-		$status = $config->statusCache();
1148
-		$this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['array']);
1149
-	}
1150
-
1151
-	public function testSetSensitiveValueArrayDatabase(): void {
1152
-		$config = $this->generateAppConfig();
1153
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1154
-		$config->clearCache();
1155
-		$this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', []));
1156
-	}
1157
-
1158
-	public function testSetNonSensitiveValueArrayAsSensitive(): void {
1159
-		$config = $this->generateAppConfig();
1160
-		$config->setValueArray('feed', 'array', ['test' => 1]);
1161
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1162
-		$this->assertSame(true, $config->isSensitive('feed', 'array'));
1163
-	}
1164
-
1165
-	public function testSetSensitiveValueArrayAsNonSensitiveStaysSensitive(): void {
1166
-		$config = $this->generateAppConfig();
1167
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1168
-		$config->setValueArray('feed', 'array', ['test' => 2]);
1169
-		$this->assertSame(true, $config->isSensitive('feed', 'array'));
1170
-	}
1171
-
1172
-	public function testSetSensitiveValueArrayAsNonSensitiveAreStillUpdated(): void {
1173
-		$config = $this->generateAppConfig();
1174
-		$config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1175
-		$config->setValueArray('feed', 'array', ['test' => 2]);
1176
-		$this->assertEqualsCanonicalizing(['test' => 2], $config->getValueArray('feed', 'array', []));
1177
-	}
1178
-
1179
-	public function testUpdateNotSensitiveToSensitive(): void {
1180
-		$config = $this->generateAppConfig();
1181
-		$config->updateSensitive('non-sensitive-app', 'lazy-key', true);
1182
-		$this->assertSame(true, $config->isSensitive('non-sensitive-app', 'lazy-key', true));
1183
-	}
1184
-
1185
-	public function testUpdateSensitiveToNotSensitive(): void {
1186
-		$config = $this->generateAppConfig();
1187
-		$config->updateSensitive('sensitive-app', 'lazy-key', false);
1188
-		$this->assertSame(false, $config->isSensitive('sensitive-app', 'lazy-key', true));
1189
-	}
1190
-
1191
-	public function testUpdateSensitiveToSensitiveReturnsFalse(): void {
1192
-		$config = $this->generateAppConfig();
1193
-		$this->assertSame(false, $config->updateSensitive('sensitive-app', 'lazy-key', true));
1194
-	}
1195
-
1196
-	public function testUpdateNotSensitiveToNotSensitiveReturnsFalse(): void {
1197
-		$config = $this->generateAppConfig();
1198
-		$this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'lazy-key', false));
1199
-	}
1200
-
1201
-	public function testUpdateSensitiveOnUnknownKeyReturnsFalse(): void {
1202
-		$config = $this->generateAppConfig();
1203
-		$this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'unknown-key', true));
1204
-	}
1205
-
1206
-	public function testUpdateNotLazyToLazy(): void {
1207
-		$config = $this->generateAppConfig();
1208
-		$config->updateLazy('non-sensitive-app', 'non-lazy-key', true);
1209
-		$this->assertSame(true, $config->isLazy('non-sensitive-app', 'non-lazy-key'));
1210
-	}
1211
-
1212
-	public function testUpdateLazyToNotLazy(): void {
1213
-		$config = $this->generateAppConfig();
1214
-		$config->updateLazy('non-sensitive-app', 'lazy-key', false);
1215
-		$this->assertSame(false, $config->isLazy('non-sensitive-app', 'lazy-key'));
1216
-	}
1217
-
1218
-	public function testUpdateLazyToLazyReturnsFalse(): void {
1219
-		$config = $this->generateAppConfig();
1220
-		$this->assertSame(false, $config->updateLazy('non-sensitive-app', 'lazy-key', true));
1221
-	}
1222
-
1223
-	public function testUpdateNotLazyToNotLazyReturnsFalse(): void {
1224
-		$config = $this->generateAppConfig();
1225
-		$this->assertSame(false, $config->updateLazy('non-sensitive-app', 'non-lazy-key', false));
1226
-	}
1227
-
1228
-	public function testUpdateLazyOnUnknownKeyReturnsFalse(): void {
1229
-		$config = $this->generateAppConfig();
1230
-		$this->assertSame(false, $config->updateLazy('non-sensitive-app', 'unknown-key', true));
1231
-	}
1232
-
1233
-	public function testGetDetails(): void {
1234
-		$config = $this->generateAppConfig();
1235
-		$this->assertEquals(
1236
-			[
1237
-				'app' => 'non-sensitive-app',
1238
-				'key' => 'lazy-key',
1239
-				'value' => 'value',
1240
-				'type' => 4,
1241
-				'lazy' => true,
1242
-				'typeString' => 'string',
1243
-				'sensitive' => false,
1244
-			],
1245
-			$config->getDetails('non-sensitive-app', 'lazy-key')
1246
-		);
1247
-	}
1248
-
1249
-	public function testGetDetailsSensitive(): void {
1250
-		$config = $this->generateAppConfig();
1251
-		$this->assertEquals(
1252
-			[
1253
-				'app' => 'sensitive-app',
1254
-				'key' => 'lazy-key',
1255
-				'value' => 'value',
1256
-				'type' => 4,
1257
-				'lazy' => true,
1258
-				'typeString' => 'string',
1259
-				'sensitive' => true,
1260
-			],
1261
-			$config->getDetails('sensitive-app', 'lazy-key')
1262
-		);
1263
-	}
1264
-
1265
-	public function testGetDetailsInt(): void {
1266
-		$config = $this->generateAppConfig();
1267
-		$this->assertEquals(
1268
-			[
1269
-				'app' => 'typed',
1270
-				'key' => 'int',
1271
-				'value' => '42',
1272
-				'type' => 8,
1273
-				'lazy' => false,
1274
-				'typeString' => 'integer',
1275
-				'sensitive' => false
1276
-			],
1277
-			$config->getDetails('typed', 'int')
1278
-		);
1279
-	}
1280
-
1281
-	public function testGetDetailsFloat(): void {
1282
-		$config = $this->generateAppConfig();
1283
-		$this->assertEquals(
1284
-			[
1285
-				'app' => 'typed',
1286
-				'key' => 'float',
1287
-				'value' => '3.14',
1288
-				'type' => 16,
1289
-				'lazy' => false,
1290
-				'typeString' => 'float',
1291
-				'sensitive' => false
1292
-			],
1293
-			$config->getDetails('typed', 'float')
1294
-		);
1295
-	}
1296
-
1297
-	public function testGetDetailsBool(): void {
1298
-		$config = $this->generateAppConfig();
1299
-		$this->assertEquals(
1300
-			[
1301
-				'app' => 'typed',
1302
-				'key' => 'bool',
1303
-				'value' => '1',
1304
-				'type' => 32,
1305
-				'lazy' => false,
1306
-				'typeString' => 'boolean',
1307
-				'sensitive' => false
1308
-			],
1309
-			$config->getDetails('typed', 'bool')
1310
-		);
1311
-	}
1312
-
1313
-	public function testGetDetailsArray(): void {
1314
-		$config = $this->generateAppConfig();
1315
-		$this->assertEquals(
1316
-			[
1317
-				'app' => 'typed',
1318
-				'key' => 'array',
1319
-				'value' => '{"test": 1}',
1320
-				'type' => 64,
1321
-				'lazy' => false,
1322
-				'typeString' => 'array',
1323
-				'sensitive' => false
1324
-			],
1325
-			$config->getDetails('typed', 'array')
1326
-		);
1327
-	}
1328
-
1329
-	public function testDeleteKey(): void {
1330
-		$config = $this->generateAppConfig();
1331
-		$config->deleteKey('anotherapp', 'key');
1332
-		$this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1333
-	}
1334
-
1335
-	public function testDeleteKeyCache(): void {
1336
-		$config = $this->generateAppConfig();
1337
-		$config->deleteKey('anotherapp', 'key');
1338
-		$status = $config->statusCache();
1339
-		$this->assertEqualsCanonicalizing(['enabled' => 'no', 'installed_version' => '3.2.1'], $status['fastCache']['anotherapp']);
1340
-	}
1341
-
1342
-	public function testDeleteKeyDatabase(): void {
1343
-		$config = $this->generateAppConfig();
1344
-		$config->deleteKey('anotherapp', 'key');
1345
-		$config->clearCache();
1346
-		$this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1347
-	}
1348
-
1349
-	public function testDeleteApp(): void {
1350
-		$config = $this->generateAppConfig();
1351
-		$config->deleteApp('anotherapp');
1352
-		$this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1353
-		$this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default'));
1354
-	}
1355
-
1356
-	public function testDeleteAppCache(): void {
1357
-		$config = $this->generateAppConfig();
1358
-		$status = $config->statusCache();
1359
-		$this->assertSame(true, isset($status['fastCache']['anotherapp']));
1360
-		$config->deleteApp('anotherapp');
1361
-		$status = $config->statusCache();
1362
-		$this->assertSame(false, isset($status['fastCache']['anotherapp']));
1363
-	}
1364
-
1365
-	public function testDeleteAppDatabase(): void {
1366
-		$config = $this->generateAppConfig();
1367
-		$config->deleteApp('anotherapp');
1368
-		$config->clearCache();
1369
-		$this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1370
-		$this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default'));
1371
-	}
1372
-
1373
-	public function testClearCache(): void {
1374
-		$config = $this->generateAppConfig();
1375
-		$config->setValueString('feed', 'string', '123454');
1376
-		$config->clearCache();
1377
-		$status = $config->statusCache();
1378
-		$this->assertSame([], $status['fastCache']);
1379
-	}
1380
-
1381
-	public function testSensitiveValuesAreEncrypted(): void {
1382
-		$key = self::getUniqueID('secret');
1383
-
1384
-		$appConfig = $this->generateAppConfig();
1385
-		$secret = md5((string)time());
1386
-		$appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1387
-
1388
-		$this->assertConfigValueNotEquals('testapp', $key, $secret);
1389
-
1390
-		// Can get in same run
1391
-		$actualSecret = $appConfig->getValueString('testapp', $key);
1392
-		$this->assertEquals($secret, $actualSecret);
1393
-
1394
-		// Can get freshly decrypted from DB
1395
-		$newAppConfig = $this->generateAppConfig();
1396
-		$actualSecret = $newAppConfig->getValueString('testapp', $key);
1397
-		$this->assertEquals($secret, $actualSecret);
1398
-	}
1399
-
1400
-	public function testMigratingNonSensitiveValueToSensitiveWithSetValue(): void {
1401
-		$key = self::getUniqueID('secret');
1402
-		$appConfig = $this->generateAppConfig();
1403
-		$secret = sha1((string)time());
1404
-
1405
-		// Unencrypted
1406
-		$appConfig->setValueString('testapp', $key, $secret);
1407
-		$this->assertConfigKey('testapp', $key, $secret);
1408
-
1409
-		// Can get freshly decrypted from DB
1410
-		$newAppConfig = $this->generateAppConfig();
1411
-		$actualSecret = $newAppConfig->getValueString('testapp', $key);
1412
-		$this->assertEquals($secret, $actualSecret);
1413
-
1414
-		// Encrypting on change
1415
-		$appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1416
-		$this->assertConfigValueNotEquals('testapp', $key, $secret);
1417
-
1418
-		// Can get in same run
1419
-		$actualSecret = $appConfig->getValueString('testapp', $key);
1420
-		$this->assertEquals($secret, $actualSecret);
1421
-
1422
-		// Can get freshly decrypted from DB
1423
-		$newAppConfig = $this->generateAppConfig();
1424
-		$actualSecret = $newAppConfig->getValueString('testapp', $key);
1425
-		$this->assertEquals($secret, $actualSecret);
1426
-	}
1427
-
1428
-	public function testUpdateSensitiveValueToNonSensitiveWithUpdateSensitive(): void {
1429
-		$key = self::getUniqueID('secret');
1430
-		$appConfig = $this->generateAppConfig();
1431
-		$secret = sha1((string)time());
1432
-
1433
-		// Encrypted
1434
-		$appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1435
-		$this->assertConfigValueNotEquals('testapp', $key, $secret);
1436
-
1437
-		// Migrate to non-sensitive / non-encrypted
1438
-		$appConfig->updateSensitive('testapp', $key, false);
1439
-		$this->assertConfigKey('testapp', $key, $secret);
1440
-	}
1441
-
1442
-	public function testUpdateNonSensitiveValueToSensitiveWithUpdateSensitive(): void {
1443
-		$key = self::getUniqueID('secret');
1444
-		$appConfig = $this->generateAppConfig();
1445
-		$secret = sha1((string)time());
1446
-
1447
-		// Unencrypted
1448
-		$appConfig->setValueString('testapp', $key, $secret);
1449
-		$this->assertConfigKey('testapp', $key, $secret);
1450
-
1451
-		// Migrate to sensitive / encrypted
1452
-		$appConfig->updateSensitive('testapp', $key, true);
1453
-		$this->assertConfigValueNotEquals('testapp', $key, $secret);
1454
-	}
1455
-
1456
-	protected function loadConfigValueFromDatabase(string $app, string $key): string|false {
1457
-		$sql = $this->connection->getQueryBuilder();
1458
-		$sql->select('configvalue')
1459
-			->from('appconfig')
1460
-			->where($sql->expr()->eq('appid', $sql->createParameter('appid')))
1461
-			->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey')))
1462
-			->setParameter('appid', $app)
1463
-			->setParameter('configkey', $key);
1464
-		$query = $sql->executeQuery();
1465
-		$actual = $query->fetchOne();
1466
-		$query->closeCursor();
1467
-
1468
-		return $actual;
1469
-	}
1470
-
1471
-	protected function assertConfigKey(string $app, string $key, string|false $expected): void {
1472
-		$this->assertEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1473
-	}
1474
-
1475
-	protected function assertConfigValueNotEquals(string $app, string $key, string|false $expected): void {
1476
-		$this->assertNotEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1477
-	}
27
+    protected IAppConfig $appConfig;
28
+    protected IDBConnection $connection;
29
+    private LoggerInterface $logger;
30
+    private ICrypto $crypto;
31
+
32
+    private array $originalConfig;
33
+
34
+    /**
35
+     * @var array<string, array<string, array<string, string, int, bool, bool>>>
36
+     *                                                                           [appId => [configKey, configValue, valueType, lazy, sensitive]]
37
+     */
38
+    private static array $baseStruct =
39
+        [
40
+            'testapp' => [
41
+                'enabled' => ['enabled', 'yes'],
42
+                'installed_version' => ['installed_version', '1.2.3'],
43
+                'depends_on' => ['depends_on', 'someapp'],
44
+                'deletethis' => ['deletethis', 'deletethis'],
45
+                'key' => ['key', 'value']
46
+            ],
47
+            'someapp' => [
48
+                'key' => ['key', 'value'],
49
+                'otherkey' => ['otherkey', 'othervalue']
50
+            ],
51
+            '123456' => [
52
+                'enabled' => ['enabled', 'yes'],
53
+                'key' => ['key', 'value']
54
+            ],
55
+            'anotherapp' => [
56
+                'enabled' => ['enabled', 'no'],
57
+                'installed_version' => ['installed_version', '3.2.1'],
58
+                'key' => ['key', 'value']
59
+            ],
60
+            'non-sensitive-app' => [
61
+                'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, false],
62
+                'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, false],
63
+            ],
64
+            'sensitive-app' => [
65
+                'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true, true],
66
+                'non-lazy-key' => ['non-lazy-key', 'value', IAppConfig::VALUE_STRING, false, true],
67
+            ],
68
+            'only-lazy' => [
69
+                'lazy-key' => ['lazy-key', 'value', IAppConfig::VALUE_STRING, true]
70
+            ],
71
+            'typed' => [
72
+                'mixed' => ['mixed', 'mix', IAppConfig::VALUE_MIXED],
73
+                'string' => ['string', 'value', IAppConfig::VALUE_STRING],
74
+                'int' => ['int', '42', IAppConfig::VALUE_INT],
75
+                'float' => ['float', '3.14', IAppConfig::VALUE_FLOAT],
76
+                'bool' => ['bool', '1', IAppConfig::VALUE_BOOL],
77
+                'array' => ['array', '{"test": 1}', IAppConfig::VALUE_ARRAY],
78
+            ],
79
+            'prefix-app' => [
80
+                'key1' => ['key1', 'value'],
81
+                'prefix1' => ['prefix1', 'value'],
82
+                'prefix-2' => ['prefix-2', 'value'],
83
+                'key-2' => ['key-2', 'value'],
84
+            ]
85
+        ];
86
+
87
+    protected function setUp(): void {
88
+        parent::setUp();
89
+
90
+        $this->connection = \OCP\Server::get(IDBConnection::class);
91
+        $this->logger = \OCP\Server::get(LoggerInterface::class);
92
+        $this->crypto = \OCP\Server::get(ICrypto::class);
93
+
94
+        // storing current config and emptying the data table
95
+        $sql = $this->connection->getQueryBuilder();
96
+        $sql->select('*')
97
+            ->from('appconfig');
98
+        $result = $sql->executeQuery();
99
+        $this->originalConfig = $result->fetchAll();
100
+        $result->closeCursor();
101
+
102
+        $sql = $this->connection->getQueryBuilder();
103
+        $sql->delete('appconfig');
104
+        $sql->executeStatement();
105
+
106
+        $sql = $this->connection->getQueryBuilder();
107
+        $sql->insert('appconfig')
108
+            ->values(
109
+                [
110
+                    'appid' => $sql->createParameter('appid'),
111
+                    'configkey' => $sql->createParameter('configkey'),
112
+                    'configvalue' => $sql->createParameter('configvalue'),
113
+                    'type' => $sql->createParameter('type'),
114
+                    'lazy' => $sql->createParameter('lazy')
115
+                ]
116
+            );
117
+
118
+        foreach (self::$baseStruct as $appId => $appData) {
119
+            foreach ($appData as $key => $row) {
120
+                $value = $row[1];
121
+                $type = $row[2] ?? IAppConfig::VALUE_MIXED;
122
+                if (($row[4] ?? false) === true) {
123
+                    $type |= IAppConfig::VALUE_SENSITIVE;
124
+                    $value = self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX') . $this->crypto->encrypt($value);
125
+                    self::$baseStruct[$appId][$key]['encrypted'] = $value;
126
+                }
127
+
128
+                $sql->setParameters(
129
+                    [
130
+                        'appid' => $appId,
131
+                        'configkey' => $row[0],
132
+                        'configvalue' => $value,
133
+                        'type' => $type,
134
+                        'lazy' => (($row[3] ?? false) === true) ? 1 : 0
135
+                    ]
136
+                )->executeStatement();
137
+            }
138
+        }
139
+    }
140
+
141
+    protected function tearDown(): void {
142
+        $sql = $this->connection->getQueryBuilder();
143
+        $sql->delete('appconfig');
144
+        $sql->executeStatement();
145
+
146
+        $sql = $this->connection->getQueryBuilder();
147
+        $sql->insert('appconfig')
148
+            ->values(
149
+                [
150
+                    'appid' => $sql->createParameter('appid'),
151
+                    'configkey' => $sql->createParameter('configkey'),
152
+                    'configvalue' => $sql->createParameter('configvalue'),
153
+                    'lazy' => $sql->createParameter('lazy'),
154
+                    'type' => $sql->createParameter('type'),
155
+                ]
156
+            );
157
+
158
+        foreach ($this->originalConfig as $key => $configs) {
159
+            $sql->setParameter('appid', $configs['appid'])
160
+                ->setParameter('configkey', $configs['configkey'])
161
+                ->setParameter('configvalue', $configs['configvalue'])
162
+                ->setParameter('lazy', ($configs['lazy'] === '1') ? '1' : '0')
163
+                ->setParameter('type', $configs['type']);
164
+            $sql->executeStatement();
165
+        }
166
+
167
+        //		$this->restoreService(AppConfig::class);
168
+        parent::tearDown();
169
+    }
170
+
171
+    /**
172
+     * @param bool $preLoading TRUE will preload the 'fast' cache, which is the normal behavior of usual
173
+     *                         IAppConfig
174
+     *
175
+     * @return IAppConfig
176
+     */
177
+    private function generateAppConfig(bool $preLoading = true): IAppConfig {
178
+        /** @var AppConfig $config */
179
+        $config = new \OC\AppConfig(
180
+            $this->connection,
181
+            $this->logger,
182
+            $this->crypto,
183
+        );
184
+        $msg = ' generateAppConfig() failed to confirm cache status';
185
+
186
+        // confirm cache status
187
+        $status = $config->statusCache();
188
+        $this->assertSame(false, $status['fastLoaded'], $msg);
189
+        $this->assertSame(false, $status['lazyLoaded'], $msg);
190
+        $this->assertSame([], $status['fastCache'], $msg);
191
+        $this->assertSame([], $status['lazyCache'], $msg);
192
+        if ($preLoading) {
193
+            // simple way to initiate the load of non-lazy config values in cache
194
+            $config->getValueString('core', 'preload', '');
195
+
196
+            // confirm cache status
197
+            $status = $config->statusCache();
198
+            $this->assertSame(true, $status['fastLoaded'], $msg);
199
+            $this->assertSame(false, $status['lazyLoaded'], $msg);
200
+
201
+            $apps = array_values(array_diff(array_keys(self::$baseStruct), ['only-lazy']));
202
+            $this->assertEqualsCanonicalizing($apps, array_keys($status['fastCache']), $msg);
203
+            $this->assertSame([], array_keys($status['lazyCache']), $msg);
204
+        }
205
+
206
+        return $config;
207
+    }
208
+
209
+    public function testGetApps(): void {
210
+        $config = $this->generateAppConfig(false);
211
+
212
+        $this->assertEqualsCanonicalizing(array_keys(self::$baseStruct), $config->getApps());
213
+    }
214
+
215
+    public function testGetAppInstalledVersions(): void {
216
+        $config = $this->generateAppConfig(false);
217
+
218
+        $this->assertEquals(
219
+            ['testapp' => '1.2.3', 'anotherapp' => '3.2.1'],
220
+            $config->getAppInstalledVersions(false)
221
+        );
222
+        $this->assertEquals(
223
+            ['testapp' => '1.2.3'],
224
+            $config->getAppInstalledVersions(true)
225
+        );
226
+    }
227
+
228
+    /**
229
+     * returns list of app and their keys
230
+     *
231
+     * @return array<string, string[]> ['appId' => ['key1', 'key2', ]]
232
+     * @see testGetKeys
233
+     */
234
+    public static function providerGetAppKeys(): array {
235
+        $appKeys = [];
236
+        foreach (self::$baseStruct as $appId => $appData) {
237
+            $keys = [];
238
+            foreach ($appData as $row) {
239
+                $keys[] = $row[0];
240
+            }
241
+            $appKeys[] = [(string)$appId, $keys];
242
+        }
243
+
244
+        return $appKeys;
245
+    }
246
+
247
+    /**
248
+     * returns list of config keys
249
+     *
250
+     * @return array<string, string, string, int, bool, bool> [appId, key, value, type, lazy, sensitive]
251
+     * @see testIsSensitive
252
+     * @see testIsLazy
253
+     * @see testGetKeys
254
+     */
255
+    public static function providerGetKeys(): array {
256
+        $appKeys = [];
257
+        foreach (self::$baseStruct as $appId => $appData) {
258
+            foreach ($appData as $row) {
259
+                $appKeys[] = [
260
+                    (string)$appId, $row[0], $row[1], $row[2] ?? IAppConfig::VALUE_MIXED, $row[3] ?? false,
261
+                    $row[4] ?? false
262
+                ];
263
+            }
264
+        }
265
+
266
+        return $appKeys;
267
+    }
268
+
269
+    /**
270
+     * @dataProvider providerGetAppKeys
271
+     *
272
+     * @param string $appId
273
+     * @param array $expectedKeys
274
+     */
275
+    public function testGetKeys(string $appId, array $expectedKeys): void {
276
+        $config = $this->generateAppConfig();
277
+        $this->assertEqualsCanonicalizing($expectedKeys, $config->getKeys($appId));
278
+    }
279
+
280
+    public function testGetKeysOnUnknownAppShouldReturnsEmptyArray(): void {
281
+        $config = $this->generateAppConfig();
282
+        $this->assertEqualsCanonicalizing([], $config->getKeys('unknown-app'));
283
+    }
284
+
285
+    /**
286
+     * @dataProvider providerGetKeys
287
+     *
288
+     * @param string $appId
289
+     * @param string $configKey
290
+     * @param string $value
291
+     * @param bool $lazy
292
+     */
293
+    public function testHasKey(string $appId, string $configKey, string $value, int $type, bool $lazy): void {
294
+        $config = $this->generateAppConfig();
295
+        $this->assertEquals(true, $config->hasKey($appId, $configKey, $lazy));
296
+    }
297
+
298
+    public function testHasKeyOnNonExistentKeyReturnsFalse(): void {
299
+        $config = $this->generateAppConfig();
300
+        $this->assertEquals(false, $config->hasKey(array_keys(self::$baseStruct)[0], 'inexistant-key'));
301
+    }
302
+
303
+    public function testHasKeyOnUnknownAppReturnsFalse(): void {
304
+        $config = $this->generateAppConfig();
305
+        $this->assertEquals(false, $config->hasKey('inexistant-app', 'inexistant-key'));
306
+    }
307
+
308
+    public function testHasKeyOnMistypedAsLazyReturnsFalse(): void {
309
+        $config = $this->generateAppConfig();
310
+        $this->assertSame(false, $config->hasKey('non-sensitive-app', 'non-lazy-key', true));
311
+    }
312
+
313
+    public function testHasKeyOnMistypeAsNonLazyReturnsFalse(): void {
314
+        $config = $this->generateAppConfig();
315
+        $this->assertSame(false, $config->hasKey('non-sensitive-app', 'lazy-key', false));
316
+    }
317
+
318
+    public function testHasKeyOnMistypeAsNonLazyReturnsTrueWithLazyArgumentIsNull(): void {
319
+        $config = $this->generateAppConfig();
320
+        $this->assertSame(true, $config->hasKey('non-sensitive-app', 'lazy-key', null));
321
+    }
322
+
323
+    /**
324
+     * @dataProvider providerGetKeys
325
+     */
326
+    public function testIsSensitive(
327
+        string $appId, string $configKey, string $configValue, int $type, bool $lazy, bool $sensitive,
328
+    ): void {
329
+        $config = $this->generateAppConfig();
330
+        $this->assertEquals($sensitive, $config->isSensitive($appId, $configKey, $lazy));
331
+    }
332
+
333
+    public function testIsSensitiveOnNonExistentKeyThrowsException(): void {
334
+        $config = $this->generateAppConfig();
335
+        $this->expectException(AppConfigUnknownKeyException::class);
336
+        $config->isSensitive(array_keys(self::$baseStruct)[0], 'inexistant-key');
337
+    }
338
+
339
+    public function testIsSensitiveOnUnknownAppThrowsException(): void {
340
+        $config = $this->generateAppConfig();
341
+        $this->expectException(AppConfigUnknownKeyException::class);
342
+        $config->isSensitive('unknown-app', 'inexistant-key');
343
+    }
344
+
345
+    public function testIsSensitiveOnSensitiveMistypedAsLazy(): void {
346
+        $config = $this->generateAppConfig();
347
+        $this->assertSame(true, $config->isSensitive('sensitive-app', 'non-lazy-key', true));
348
+    }
349
+
350
+    public function testIsSensitiveOnNonSensitiveMistypedAsLazy(): void {
351
+        $config = $this->generateAppConfig();
352
+        $this->assertSame(false, $config->isSensitive('non-sensitive-app', 'non-lazy-key', true));
353
+    }
354
+
355
+    public function testIsSensitiveOnSensitiveMistypedAsNonLazyThrowsException(): void {
356
+        $config = $this->generateAppConfig();
357
+        $this->expectException(AppConfigUnknownKeyException::class);
358
+        $config->isSensitive('sensitive-app', 'lazy-key', false);
359
+    }
360
+
361
+    public function testIsSensitiveOnNonSensitiveMistypedAsNonLazyThrowsException(): void {
362
+        $config = $this->generateAppConfig();
363
+        $this->expectException(AppConfigUnknownKeyException::class);
364
+        $config->isSensitive('non-sensitive-app', 'lazy-key', false);
365
+    }
366
+
367
+    /**
368
+     * @dataProvider providerGetKeys
369
+     */
370
+    public function testIsLazy(string $appId, string $configKey, string $configValue, int $type, bool $lazy,
371
+    ): void {
372
+        $config = $this->generateAppConfig();
373
+        $this->assertEquals($lazy, $config->isLazy($appId, $configKey));
374
+    }
375
+
376
+    public function testIsLazyOnNonExistentKeyThrowsException(): void {
377
+        $config = $this->generateAppConfig();
378
+        $this->expectException(AppConfigUnknownKeyException::class);
379
+        $config->isLazy(array_keys(self::$baseStruct)[0], 'inexistant-key');
380
+    }
381
+
382
+    public function testIsLazyOnUnknownAppThrowsException(): void {
383
+        $config = $this->generateAppConfig();
384
+        $this->expectException(AppConfigUnknownKeyException::class);
385
+        $config->isLazy('unknown-app', 'inexistant-key');
386
+    }
387
+
388
+    public function testGetAllValues(): void {
389
+        $config = $this->generateAppConfig();
390
+        $this->assertEquals(
391
+            [
392
+                'array' => ['test' => 1],
393
+                'bool' => true,
394
+                'float' => 3.14,
395
+                'int' => 42,
396
+                'mixed' => 'mix',
397
+                'string' => 'value',
398
+            ],
399
+            $config->getAllValues('typed')
400
+        );
401
+    }
402
+
403
+    public function testGetAllValuesWithEmptyApp(): void {
404
+        $config = $this->generateAppConfig();
405
+        $this->expectException(InvalidArgumentException::class);
406
+        $config->getAllValues('');
407
+    }
408
+
409
+    /**
410
+     * @dataProvider providerGetAppKeys
411
+     *
412
+     * @param string $appId
413
+     * @param array $keys
414
+     */
415
+    public function testGetAllValuesWithEmptyKey(string $appId, array $keys): void {
416
+        $config = $this->generateAppConfig();
417
+        $this->assertEqualsCanonicalizing($keys, array_keys($config->getAllValues($appId, '')));
418
+    }
419
+
420
+    public function testGetAllValuesWithPrefix(): void {
421
+        $config = $this->generateAppConfig();
422
+        $this->assertEqualsCanonicalizing(['prefix1', 'prefix-2'], array_keys($config->getAllValues('prefix-app', 'prefix')));
423
+    }
424
+
425
+    public function testSearchValues(): void {
426
+        $config = $this->generateAppConfig();
427
+        $this->assertEqualsCanonicalizing(['testapp' => 'yes', '123456' => 'yes', 'anotherapp' => 'no'], $config->searchValues('enabled'));
428
+    }
429
+
430
+    public function testGetValueString(): void {
431
+        $config = $this->generateAppConfig();
432
+        $this->assertSame('value', $config->getValueString('typed', 'string', ''));
433
+    }
434
+
435
+    public function testGetValueStringOnUnknownAppReturnsDefault(): void {
436
+        $config = $this->generateAppConfig();
437
+        $this->assertSame('default-1', $config->getValueString('typed-1', 'string', 'default-1'));
438
+    }
439
+
440
+    public function testGetValueStringOnNonExistentKeyReturnsDefault(): void {
441
+        $config = $this->generateAppConfig();
442
+        $this->assertSame('default-2', $config->getValueString('typed', 'string-2', 'default-2'));
443
+    }
444
+
445
+    public function testGetValueStringOnWrongType(): void {
446
+        $config = $this->generateAppConfig();
447
+        $this->expectException(AppConfigTypeConflictException::class);
448
+        $config->getValueString('typed', 'int');
449
+    }
450
+
451
+    public function testGetNonLazyValueStringAsLazy(): void {
452
+        $config = $this->generateAppConfig();
453
+        $this->assertSame('value', $config->getValueString('non-sensitive-app', 'non-lazy-key', 'default', lazy: true));
454
+    }
455
+
456
+    public function testGetValueInt(): void {
457
+        $config = $this->generateAppConfig();
458
+        $this->assertSame(42, $config->getValueInt('typed', 'int', 0));
459
+    }
460
+
461
+    public function testGetValueIntOnUnknownAppReturnsDefault(): void {
462
+        $config = $this->generateAppConfig();
463
+        $this->assertSame(1, $config->getValueInt('typed-1', 'int', 1));
464
+    }
465
+
466
+    public function testGetValueIntOnNonExistentKeyReturnsDefault(): void {
467
+        $config = $this->generateAppConfig();
468
+        $this->assertSame(2, $config->getValueInt('typed', 'int-2', 2));
469
+    }
470
+
471
+    public function testGetValueIntOnWrongType(): void {
472
+        $config = $this->generateAppConfig();
473
+        $this->expectException(AppConfigTypeConflictException::class);
474
+        $config->getValueInt('typed', 'float');
475
+    }
476
+
477
+    public function testGetValueFloat(): void {
478
+        $config = $this->generateAppConfig();
479
+        $this->assertSame(3.14, $config->getValueFloat('typed', 'float', 0));
480
+    }
481
+
482
+    public function testGetValueFloatOnNonUnknownAppReturnsDefault(): void {
483
+        $config = $this->generateAppConfig();
484
+        $this->assertSame(1.11, $config->getValueFloat('typed-1', 'float', 1.11));
485
+    }
486
+
487
+    public function testGetValueFloatOnNonExistentKeyReturnsDefault(): void {
488
+        $config = $this->generateAppConfig();
489
+        $this->assertSame(2.22, $config->getValueFloat('typed', 'float-2', 2.22));
490
+    }
491
+
492
+    public function testGetValueFloatOnWrongType(): void {
493
+        $config = $this->generateAppConfig();
494
+        $this->expectException(AppConfigTypeConflictException::class);
495
+        $config->getValueFloat('typed', 'bool');
496
+    }
497
+
498
+    public function testGetValueBool(): void {
499
+        $config = $this->generateAppConfig();
500
+        $this->assertSame(true, $config->getValueBool('typed', 'bool'));
501
+    }
502
+
503
+    public function testGetValueBoolOnUnknownAppReturnsDefault(): void {
504
+        $config = $this->generateAppConfig();
505
+        $this->assertSame(false, $config->getValueBool('typed-1', 'bool', false));
506
+    }
507
+
508
+    public function testGetValueBoolOnNonExistentKeyReturnsDefault(): void {
509
+        $config = $this->generateAppConfig();
510
+        $this->assertSame(false, $config->getValueBool('typed', 'bool-2'));
511
+    }
512
+
513
+    public function testGetValueBoolOnWrongType(): void {
514
+        $config = $this->generateAppConfig();
515
+        $this->expectException(AppConfigTypeConflictException::class);
516
+        $config->getValueBool('typed', 'array');
517
+    }
518
+
519
+    public function testGetValueArray(): void {
520
+        $config = $this->generateAppConfig();
521
+        $this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('typed', 'array', []));
522
+    }
523
+
524
+    public function testGetValueArrayOnUnknownAppReturnsDefault(): void {
525
+        $config = $this->generateAppConfig();
526
+        $this->assertSame([1], $config->getValueArray('typed-1', 'array', [1]));
527
+    }
528
+
529
+    public function testGetValueArrayOnNonExistentKeyReturnsDefault(): void {
530
+        $config = $this->generateAppConfig();
531
+        $this->assertSame([1, 2], $config->getValueArray('typed', 'array-2', [1, 2]));
532
+    }
533
+
534
+    public function testGetValueArrayOnWrongType(): void {
535
+        $config = $this->generateAppConfig();
536
+        $this->expectException(AppConfigTypeConflictException::class);
537
+        $config->getValueArray('typed', 'string');
538
+    }
539
+
540
+
541
+    /**
542
+     * @return array
543
+     * @see testGetValueType
544
+     *
545
+     * @see testGetValueMixed
546
+     */
547
+    public static function providerGetValueMixed(): array {
548
+        return [
549
+            // key, value, type
550
+            ['mixed', 'mix', IAppConfig::VALUE_MIXED],
551
+            ['string', 'value', IAppConfig::VALUE_STRING],
552
+            ['int', '42', IAppConfig::VALUE_INT],
553
+            ['float', '3.14', IAppConfig::VALUE_FLOAT],
554
+            ['bool', '1', IAppConfig::VALUE_BOOL],
555
+            ['array', '{"test": 1}', IAppConfig::VALUE_ARRAY],
556
+        ];
557
+    }
558
+
559
+    /**
560
+     * @dataProvider providerGetValueMixed
561
+     *
562
+     * @param string $key
563
+     * @param string $value
564
+     */
565
+    public function testGetValueMixed(string $key, string $value): void {
566
+        $config = $this->generateAppConfig();
567
+        $this->assertSame($value, $config->getValueMixed('typed', $key));
568
+    }
569
+
570
+    /**
571
+     * @dataProvider providerGetValueMixed
572
+     *
573
+     * @param string $key
574
+     * @param string $value
575
+     * @param int $type
576
+     */
577
+    public function testGetValueType(string $key, string $value, int $type): void {
578
+        $config = $this->generateAppConfig();
579
+        $this->assertSame($type, $config->getValueType('typed', $key));
580
+    }
581
+
582
+    public function testGetValueTypeOnUnknownApp(): void {
583
+        $config = $this->generateAppConfig();
584
+        $this->expectException(AppConfigUnknownKeyException::class);
585
+        $config->getValueType('typed-1', 'string');
586
+    }
587
+
588
+    public function testGetValueTypeOnNonExistentKey(): void {
589
+        $config = $this->generateAppConfig();
590
+        $this->expectException(AppConfigUnknownKeyException::class);
591
+        $config->getValueType('typed', 'string-2');
592
+    }
593
+
594
+    public function testSetValueString(): void {
595
+        $config = $this->generateAppConfig();
596
+        $config->setValueString('feed', 'string', 'value-1');
597
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
598
+    }
599
+
600
+    public function testSetValueStringCache(): void {
601
+        $config = $this->generateAppConfig();
602
+        $config->setValueString('feed', 'string', 'value-1');
603
+        $status = $config->statusCache();
604
+        $this->assertSame('value-1', $status['fastCache']['feed']['string']);
605
+    }
606
+
607
+    public function testSetValueStringDatabase(): void {
608
+        $config = $this->generateAppConfig();
609
+        $config->setValueString('feed', 'string', 'value-1');
610
+        $config->clearCache();
611
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
612
+    }
613
+
614
+    public function testSetValueStringIsUpdated(): void {
615
+        $config = $this->generateAppConfig();
616
+        $config->setValueString('feed', 'string', 'value-1');
617
+        $this->assertSame(true, $config->setValueString('feed', 'string', 'value-2'));
618
+    }
619
+
620
+    public function testSetValueStringIsNotUpdated(): void {
621
+        $config = $this->generateAppConfig();
622
+        $config->setValueString('feed', 'string', 'value-1');
623
+        $this->assertSame(false, $config->setValueString('feed', 'string', 'value-1'));
624
+    }
625
+
626
+    public function testSetValueStringIsUpdatedCache(): void {
627
+        $config = $this->generateAppConfig();
628
+        $config->setValueString('feed', 'string', 'value-1');
629
+        $config->setValueString('feed', 'string', 'value-2');
630
+        $status = $config->statusCache();
631
+        $this->assertSame('value-2', $status['fastCache']['feed']['string']);
632
+    }
633
+
634
+    public function testSetValueStringIsUpdatedDatabase(): void {
635
+        $config = $this->generateAppConfig();
636
+        $config->setValueString('feed', 'string', 'value-1');
637
+        $config->setValueString('feed', 'string', 'value-2');
638
+        $config->clearCache();
639
+        $this->assertSame('value-2', $config->getValueString('feed', 'string', ''));
640
+    }
641
+
642
+    public function testSetValueInt(): void {
643
+        $config = $this->generateAppConfig();
644
+        $config->setValueInt('feed', 'int', 42);
645
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
646
+    }
647
+
648
+    public function testSetValueIntCache(): void {
649
+        $config = $this->generateAppConfig();
650
+        $config->setValueInt('feed', 'int', 42);
651
+        $status = $config->statusCache();
652
+        $this->assertSame('42', $status['fastCache']['feed']['int']);
653
+    }
654
+
655
+    public function testSetValueIntDatabase(): void {
656
+        $config = $this->generateAppConfig();
657
+        $config->setValueInt('feed', 'int', 42);
658
+        $config->clearCache();
659
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
660
+    }
661
+
662
+    public function testSetValueIntIsUpdated(): void {
663
+        $config = $this->generateAppConfig();
664
+        $config->setValueInt('feed', 'int', 42);
665
+        $this->assertSame(true, $config->setValueInt('feed', 'int', 17));
666
+    }
667
+
668
+    public function testSetValueIntIsNotUpdated(): void {
669
+        $config = $this->generateAppConfig();
670
+        $config->setValueInt('feed', 'int', 42);
671
+        $this->assertSame(false, $config->setValueInt('feed', 'int', 42));
672
+    }
673
+
674
+    public function testSetValueIntIsUpdatedCache(): void {
675
+        $config = $this->generateAppConfig();
676
+        $config->setValueInt('feed', 'int', 42);
677
+        $config->setValueInt('feed', 'int', 17);
678
+        $status = $config->statusCache();
679
+        $this->assertSame('17', $status['fastCache']['feed']['int']);
680
+    }
681
+
682
+    public function testSetValueIntIsUpdatedDatabase(): void {
683
+        $config = $this->generateAppConfig();
684
+        $config->setValueInt('feed', 'int', 42);
685
+        $config->setValueInt('feed', 'int', 17);
686
+        $config->clearCache();
687
+        $this->assertSame(17, $config->getValueInt('feed', 'int', 0));
688
+    }
689
+
690
+    public function testSetValueFloat(): void {
691
+        $config = $this->generateAppConfig();
692
+        $config->setValueFloat('feed', 'float', 3.14);
693
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
694
+    }
695
+
696
+    public function testSetValueFloatCache(): void {
697
+        $config = $this->generateAppConfig();
698
+        $config->setValueFloat('feed', 'float', 3.14);
699
+        $status = $config->statusCache();
700
+        $this->assertSame('3.14', $status['fastCache']['feed']['float']);
701
+    }
702
+
703
+    public function testSetValueFloatDatabase(): void {
704
+        $config = $this->generateAppConfig();
705
+        $config->setValueFloat('feed', 'float', 3.14);
706
+        $config->clearCache();
707
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
708
+    }
709
+
710
+    public function testSetValueFloatIsUpdated(): void {
711
+        $config = $this->generateAppConfig();
712
+        $config->setValueFloat('feed', 'float', 3.14);
713
+        $this->assertSame(true, $config->setValueFloat('feed', 'float', 1.23));
714
+    }
715
+
716
+    public function testSetValueFloatIsNotUpdated(): void {
717
+        $config = $this->generateAppConfig();
718
+        $config->setValueFloat('feed', 'float', 3.14);
719
+        $this->assertSame(false, $config->setValueFloat('feed', 'float', 3.14));
720
+    }
721
+
722
+    public function testSetValueFloatIsUpdatedCache(): void {
723
+        $config = $this->generateAppConfig();
724
+        $config->setValueFloat('feed', 'float', 3.14);
725
+        $config->setValueFloat('feed', 'float', 1.23);
726
+        $status = $config->statusCache();
727
+        $this->assertSame('1.23', $status['fastCache']['feed']['float']);
728
+    }
729
+
730
+    public function testSetValueFloatIsUpdatedDatabase(): void {
731
+        $config = $this->generateAppConfig();
732
+        $config->setValueFloat('feed', 'float', 3.14);
733
+        $config->setValueFloat('feed', 'float', 1.23);
734
+        $config->clearCache();
735
+        $this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0));
736
+    }
737
+
738
+    public function testSetValueBool(): void {
739
+        $config = $this->generateAppConfig();
740
+        $config->setValueBool('feed', 'bool', true);
741
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false));
742
+    }
743
+
744
+    public function testSetValueBoolCache(): void {
745
+        $config = $this->generateAppConfig();
746
+        $config->setValueBool('feed', 'bool', true);
747
+        $status = $config->statusCache();
748
+        $this->assertSame('1', $status['fastCache']['feed']['bool']);
749
+    }
750
+
751
+    public function testSetValueBoolDatabase(): void {
752
+        $config = $this->generateAppConfig();
753
+        $config->setValueBool('feed', 'bool', true);
754
+        $config->clearCache();
755
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false));
756
+    }
757
+
758
+    public function testSetValueBoolIsUpdated(): void {
759
+        $config = $this->generateAppConfig();
760
+        $config->setValueBool('feed', 'bool', true);
761
+        $this->assertSame(true, $config->setValueBool('feed', 'bool', false));
762
+    }
763
+
764
+    public function testSetValueBoolIsNotUpdated(): void {
765
+        $config = $this->generateAppConfig();
766
+        $config->setValueBool('feed', 'bool', true);
767
+        $this->assertSame(false, $config->setValueBool('feed', 'bool', true));
768
+    }
769
+
770
+    public function testSetValueBoolIsUpdatedCache(): void {
771
+        $config = $this->generateAppConfig();
772
+        $config->setValueBool('feed', 'bool', true);
773
+        $config->setValueBool('feed', 'bool', false);
774
+        $status = $config->statusCache();
775
+        $this->assertSame('0', $status['fastCache']['feed']['bool']);
776
+    }
777
+
778
+    public function testSetValueBoolIsUpdatedDatabase(): void {
779
+        $config = $this->generateAppConfig();
780
+        $config->setValueBool('feed', 'bool', true);
781
+        $config->setValueBool('feed', 'bool', false);
782
+        $config->clearCache();
783
+        $this->assertSame(false, $config->getValueBool('feed', 'bool', true));
784
+    }
785
+
786
+
787
+    public function testSetValueArray(): void {
788
+        $config = $this->generateAppConfig();
789
+        $config->setValueArray('feed', 'array', ['test' => 1]);
790
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
791
+    }
792
+
793
+    public function testSetValueArrayCache(): void {
794
+        $config = $this->generateAppConfig();
795
+        $config->setValueArray('feed', 'array', ['test' => 1]);
796
+        $status = $config->statusCache();
797
+        $this->assertSame('{"test":1}', $status['fastCache']['feed']['array']);
798
+    }
799
+
800
+    public function testSetValueArrayDatabase(): void {
801
+        $config = $this->generateAppConfig();
802
+        $config->setValueArray('feed', 'array', ['test' => 1]);
803
+        $config->clearCache();
804
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
805
+    }
806
+
807
+    public function testSetValueArrayIsUpdated(): void {
808
+        $config = $this->generateAppConfig();
809
+        $config->setValueArray('feed', 'array', ['test' => 1]);
810
+        $this->assertSame(true, $config->setValueArray('feed', 'array', ['test' => 2]));
811
+    }
812
+
813
+    public function testSetValueArrayIsNotUpdated(): void {
814
+        $config = $this->generateAppConfig();
815
+        $config->setValueArray('feed', 'array', ['test' => 1]);
816
+        $this->assertSame(false, $config->setValueArray('feed', 'array', ['test' => 1]));
817
+    }
818
+
819
+    public function testSetValueArrayIsUpdatedCache(): void {
820
+        $config = $this->generateAppConfig();
821
+        $config->setValueArray('feed', 'array', ['test' => 1]);
822
+        $config->setValueArray('feed', 'array', ['test' => 2]);
823
+        $status = $config->statusCache();
824
+        $this->assertSame('{"test":2}', $status['fastCache']['feed']['array']);
825
+    }
826
+
827
+    public function testSetValueArrayIsUpdatedDatabase(): void {
828
+        $config = $this->generateAppConfig();
829
+        $config->setValueArray('feed', 'array', ['test' => 1]);
830
+        $config->setValueArray('feed', 'array', ['test' => 2]);
831
+        $config->clearCache();
832
+        $this->assertSame(['test' => 2], $config->getValueArray('feed', 'array', []));
833
+    }
834
+
835
+    public function testSetLazyValueString(): void {
836
+        $config = $this->generateAppConfig();
837
+        $config->setValueString('feed', 'string', 'value-1', true);
838
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
839
+    }
840
+
841
+    public function testSetLazyValueStringCache(): void {
842
+        $config = $this->generateAppConfig();
843
+        $config->setValueString('feed', 'string', 'value-1', true);
844
+        $status = $config->statusCache();
845
+        $this->assertSame('value-1', $status['lazyCache']['feed']['string']);
846
+    }
847
+
848
+    public function testSetLazyValueStringDatabase(): void {
849
+        $config = $this->generateAppConfig();
850
+        $config->setValueString('feed', 'string', 'value-1', true);
851
+        $config->clearCache();
852
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
853
+    }
854
+
855
+    public function testSetLazyValueStringAsNonLazy(): void {
856
+        $config = $this->generateAppConfig();
857
+        $config->setValueString('feed', 'string', 'value-1', true);
858
+        $config->setValueString('feed', 'string', 'value-1', false);
859
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
860
+    }
861
+
862
+    public function testSetNonLazyValueStringAsLazy(): void {
863
+        $config = $this->generateAppConfig();
864
+        $config->setValueString('feed', 'string', 'value-1', false);
865
+        $config->setValueString('feed', 'string', 'value-1', true);
866
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', '', true));
867
+    }
868
+
869
+    public function testSetSensitiveValueString(): void {
870
+        $config = $this->generateAppConfig();
871
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
872
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
873
+    }
874
+
875
+    public function testSetSensitiveValueStringCache(): void {
876
+        $config = $this->generateAppConfig();
877
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
878
+        $status = $config->statusCache();
879
+        $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['string']);
880
+    }
881
+
882
+    public function testSetSensitiveValueStringDatabase(): void {
883
+        $config = $this->generateAppConfig();
884
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
885
+        $config->clearCache();
886
+        $this->assertSame('value-1', $config->getValueString('feed', 'string', ''));
887
+    }
888
+
889
+    public function testSetNonSensitiveValueStringAsSensitive(): void {
890
+        $config = $this->generateAppConfig();
891
+        $config->setValueString('feed', 'string', 'value-1', sensitive: false);
892
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
893
+        $this->assertSame(true, $config->isSensitive('feed', 'string'));
894
+
895
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-1');
896
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-2');
897
+    }
898
+
899
+    public function testSetSensitiveValueStringAsNonSensitiveStaysSensitive(): void {
900
+        $config = $this->generateAppConfig();
901
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
902
+        $config->setValueString('feed', 'string', 'value-2', sensitive: false);
903
+        $this->assertSame(true, $config->isSensitive('feed', 'string'));
904
+
905
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-1');
906
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-2');
907
+    }
908
+
909
+    public function testSetSensitiveValueStringAsNonSensitiveAreStillUpdated(): void {
910
+        $config = $this->generateAppConfig();
911
+        $config->setValueString('feed', 'string', 'value-1', sensitive: true);
912
+        $config->setValueString('feed', 'string', 'value-2', sensitive: false);
913
+        $this->assertSame('value-2', $config->getValueString('feed', 'string', ''));
914
+
915
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-1');
916
+        $this->assertConfigValueNotEquals('feed', 'string', 'value-2');
917
+    }
918
+
919
+    public function testSetLazyValueInt(): void {
920
+        $config = $this->generateAppConfig();
921
+        $config->setValueInt('feed', 'int', 42, true);
922
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
923
+    }
924
+
925
+    public function testSetLazyValueIntCache(): void {
926
+        $config = $this->generateAppConfig();
927
+        $config->setValueInt('feed', 'int', 42, true);
928
+        $status = $config->statusCache();
929
+        $this->assertSame('42', $status['lazyCache']['feed']['int']);
930
+    }
931
+
932
+    public function testSetLazyValueIntDatabase(): void {
933
+        $config = $this->generateAppConfig();
934
+        $config->setValueInt('feed', 'int', 42, true);
935
+        $config->clearCache();
936
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
937
+    }
938
+
939
+    public function testSetLazyValueIntAsNonLazy(): void {
940
+        $config = $this->generateAppConfig();
941
+        $config->setValueInt('feed', 'int', 42, true);
942
+        $config->setValueInt('feed', 'int', 42, false);
943
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
944
+    }
945
+
946
+    public function testSetNonLazyValueIntAsLazy(): void {
947
+        $config = $this->generateAppConfig();
948
+        $config->setValueInt('feed', 'int', 42, false);
949
+        $config->setValueInt('feed', 'int', 42, true);
950
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0, true));
951
+    }
952
+
953
+    public function testSetSensitiveValueInt(): void {
954
+        $config = $this->generateAppConfig();
955
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
956
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
957
+    }
958
+
959
+    public function testSetSensitiveValueIntCache(): void {
960
+        $config = $this->generateAppConfig();
961
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
962
+        $status = $config->statusCache();
963
+        $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['int']);
964
+    }
965
+
966
+    public function testSetSensitiveValueIntDatabase(): void {
967
+        $config = $this->generateAppConfig();
968
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
969
+        $config->clearCache();
970
+        $this->assertSame(42, $config->getValueInt('feed', 'int', 0));
971
+    }
972
+
973
+    public function testSetNonSensitiveValueIntAsSensitive(): void {
974
+        $config = $this->generateAppConfig();
975
+        $config->setValueInt('feed', 'int', 42);
976
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
977
+        $this->assertSame(true, $config->isSensitive('feed', 'int'));
978
+    }
979
+
980
+    public function testSetSensitiveValueIntAsNonSensitiveStaysSensitive(): void {
981
+        $config = $this->generateAppConfig();
982
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
983
+        $config->setValueInt('feed', 'int', 17);
984
+        $this->assertSame(true, $config->isSensitive('feed', 'int'));
985
+    }
986
+
987
+    public function testSetSensitiveValueIntAsNonSensitiveAreStillUpdated(): void {
988
+        $config = $this->generateAppConfig();
989
+        $config->setValueInt('feed', 'int', 42, sensitive: true);
990
+        $config->setValueInt('feed', 'int', 17);
991
+        $this->assertSame(17, $config->getValueInt('feed', 'int', 0));
992
+    }
993
+
994
+    public function testSetLazyValueFloat(): void {
995
+        $config = $this->generateAppConfig();
996
+        $config->setValueFloat('feed', 'float', 3.14, true);
997
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
998
+    }
999
+
1000
+    public function testSetLazyValueFloatCache(): void {
1001
+        $config = $this->generateAppConfig();
1002
+        $config->setValueFloat('feed', 'float', 3.14, true);
1003
+        $status = $config->statusCache();
1004
+        $this->assertSame('3.14', $status['lazyCache']['feed']['float']);
1005
+    }
1006
+
1007
+    public function testSetLazyValueFloatDatabase(): void {
1008
+        $config = $this->generateAppConfig();
1009
+        $config->setValueFloat('feed', 'float', 3.14, true);
1010
+        $config->clearCache();
1011
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1012
+    }
1013
+
1014
+    public function testSetLazyValueFloatAsNonLazy(): void {
1015
+        $config = $this->generateAppConfig();
1016
+        $config->setValueFloat('feed', 'float', 3.14, true);
1017
+        $config->setValueFloat('feed', 'float', 3.14, false);
1018
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1019
+    }
1020
+
1021
+    public function testSetNonLazyValueFloatAsLazy(): void {
1022
+        $config = $this->generateAppConfig();
1023
+        $config->setValueFloat('feed', 'float', 3.14, false);
1024
+        $config->setValueFloat('feed', 'float', 3.14, true);
1025
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0, true));
1026
+    }
1027
+
1028
+    public function testSetSensitiveValueFloat(): void {
1029
+        $config = $this->generateAppConfig();
1030
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1031
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1032
+    }
1033
+
1034
+    public function testSetSensitiveValueFloatCache(): void {
1035
+        $config = $this->generateAppConfig();
1036
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1037
+        $status = $config->statusCache();
1038
+        $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['float']);
1039
+    }
1040
+
1041
+    public function testSetSensitiveValueFloatDatabase(): void {
1042
+        $config = $this->generateAppConfig();
1043
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1044
+        $config->clearCache();
1045
+        $this->assertSame(3.14, $config->getValueFloat('feed', 'float', 0));
1046
+    }
1047
+
1048
+    public function testSetNonSensitiveValueFloatAsSensitive(): void {
1049
+        $config = $this->generateAppConfig();
1050
+        $config->setValueFloat('feed', 'float', 3.14);
1051
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1052
+        $this->assertSame(true, $config->isSensitive('feed', 'float'));
1053
+    }
1054
+
1055
+    public function testSetSensitiveValueFloatAsNonSensitiveStaysSensitive(): void {
1056
+        $config = $this->generateAppConfig();
1057
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1058
+        $config->setValueFloat('feed', 'float', 1.23);
1059
+        $this->assertSame(true, $config->isSensitive('feed', 'float'));
1060
+    }
1061
+
1062
+    public function testSetSensitiveValueFloatAsNonSensitiveAreStillUpdated(): void {
1063
+        $config = $this->generateAppConfig();
1064
+        $config->setValueFloat('feed', 'float', 3.14, sensitive: true);
1065
+        $config->setValueFloat('feed', 'float', 1.23);
1066
+        $this->assertSame(1.23, $config->getValueFloat('feed', 'float', 0));
1067
+    }
1068
+
1069
+    public function testSetLazyValueBool(): void {
1070
+        $config = $this->generateAppConfig();
1071
+        $config->setValueBool('feed', 'bool', true, true);
1072
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1073
+    }
1074
+
1075
+    public function testSetLazyValueBoolCache(): void {
1076
+        $config = $this->generateAppConfig();
1077
+        $config->setValueBool('feed', 'bool', true, true);
1078
+        $status = $config->statusCache();
1079
+        $this->assertSame('1', $status['lazyCache']['feed']['bool']);
1080
+    }
1081
+
1082
+    public function testSetLazyValueBoolDatabase(): void {
1083
+        $config = $this->generateAppConfig();
1084
+        $config->setValueBool('feed', 'bool', true, true);
1085
+        $config->clearCache();
1086
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1087
+    }
1088
+
1089
+    public function testSetLazyValueBoolAsNonLazy(): void {
1090
+        $config = $this->generateAppConfig();
1091
+        $config->setValueBool('feed', 'bool', true, true);
1092
+        $config->setValueBool('feed', 'bool', true, false);
1093
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false));
1094
+    }
1095
+
1096
+    public function testSetNonLazyValueBoolAsLazy(): void {
1097
+        $config = $this->generateAppConfig();
1098
+        $config->setValueBool('feed', 'bool', true, false);
1099
+        $config->setValueBool('feed', 'bool', true, true);
1100
+        $this->assertSame(true, $config->getValueBool('feed', 'bool', false, true));
1101
+    }
1102
+
1103
+    public function testSetLazyValueArray(): void {
1104
+        $config = $this->generateAppConfig();
1105
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1106
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1107
+    }
1108
+
1109
+    public function testSetLazyValueArrayCache(): void {
1110
+        $config = $this->generateAppConfig();
1111
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1112
+        $status = $config->statusCache();
1113
+        $this->assertSame('{"test":1}', $status['lazyCache']['feed']['array']);
1114
+    }
1115
+
1116
+    public function testSetLazyValueArrayDatabase(): void {
1117
+        $config = $this->generateAppConfig();
1118
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1119
+        $config->clearCache();
1120
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1121
+    }
1122
+
1123
+    public function testSetLazyValueArrayAsNonLazy(): void {
1124
+        $config = $this->generateAppConfig();
1125
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1126
+        $config->setValueArray('feed', 'array', ['test' => 1], false);
1127
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', []));
1128
+    }
1129
+
1130
+    public function testSetNonLazyValueArrayAsLazy(): void {
1131
+        $config = $this->generateAppConfig();
1132
+        $config->setValueArray('feed', 'array', ['test' => 1], false);
1133
+        $config->setValueArray('feed', 'array', ['test' => 1], true);
1134
+        $this->assertSame(['test' => 1], $config->getValueArray('feed', 'array', [], true));
1135
+    }
1136
+
1137
+
1138
+    public function testSetSensitiveValueArray(): void {
1139
+        $config = $this->generateAppConfig();
1140
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1141
+        $this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', []));
1142
+    }
1143
+
1144
+    public function testSetSensitiveValueArrayCache(): void {
1145
+        $config = $this->generateAppConfig();
1146
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1147
+        $status = $config->statusCache();
1148
+        $this->assertStringStartsWith(self::invokePrivate(AppConfig::class, 'ENCRYPTION_PREFIX'), $status['fastCache']['feed']['array']);
1149
+    }
1150
+
1151
+    public function testSetSensitiveValueArrayDatabase(): void {
1152
+        $config = $this->generateAppConfig();
1153
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1154
+        $config->clearCache();
1155
+        $this->assertEqualsCanonicalizing(['test' => 1], $config->getValueArray('feed', 'array', []));
1156
+    }
1157
+
1158
+    public function testSetNonSensitiveValueArrayAsSensitive(): void {
1159
+        $config = $this->generateAppConfig();
1160
+        $config->setValueArray('feed', 'array', ['test' => 1]);
1161
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1162
+        $this->assertSame(true, $config->isSensitive('feed', 'array'));
1163
+    }
1164
+
1165
+    public function testSetSensitiveValueArrayAsNonSensitiveStaysSensitive(): void {
1166
+        $config = $this->generateAppConfig();
1167
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1168
+        $config->setValueArray('feed', 'array', ['test' => 2]);
1169
+        $this->assertSame(true, $config->isSensitive('feed', 'array'));
1170
+    }
1171
+
1172
+    public function testSetSensitiveValueArrayAsNonSensitiveAreStillUpdated(): void {
1173
+        $config = $this->generateAppConfig();
1174
+        $config->setValueArray('feed', 'array', ['test' => 1], sensitive: true);
1175
+        $config->setValueArray('feed', 'array', ['test' => 2]);
1176
+        $this->assertEqualsCanonicalizing(['test' => 2], $config->getValueArray('feed', 'array', []));
1177
+    }
1178
+
1179
+    public function testUpdateNotSensitiveToSensitive(): void {
1180
+        $config = $this->generateAppConfig();
1181
+        $config->updateSensitive('non-sensitive-app', 'lazy-key', true);
1182
+        $this->assertSame(true, $config->isSensitive('non-sensitive-app', 'lazy-key', true));
1183
+    }
1184
+
1185
+    public function testUpdateSensitiveToNotSensitive(): void {
1186
+        $config = $this->generateAppConfig();
1187
+        $config->updateSensitive('sensitive-app', 'lazy-key', false);
1188
+        $this->assertSame(false, $config->isSensitive('sensitive-app', 'lazy-key', true));
1189
+    }
1190
+
1191
+    public function testUpdateSensitiveToSensitiveReturnsFalse(): void {
1192
+        $config = $this->generateAppConfig();
1193
+        $this->assertSame(false, $config->updateSensitive('sensitive-app', 'lazy-key', true));
1194
+    }
1195
+
1196
+    public function testUpdateNotSensitiveToNotSensitiveReturnsFalse(): void {
1197
+        $config = $this->generateAppConfig();
1198
+        $this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'lazy-key', false));
1199
+    }
1200
+
1201
+    public function testUpdateSensitiveOnUnknownKeyReturnsFalse(): void {
1202
+        $config = $this->generateAppConfig();
1203
+        $this->assertSame(false, $config->updateSensitive('non-sensitive-app', 'unknown-key', true));
1204
+    }
1205
+
1206
+    public function testUpdateNotLazyToLazy(): void {
1207
+        $config = $this->generateAppConfig();
1208
+        $config->updateLazy('non-sensitive-app', 'non-lazy-key', true);
1209
+        $this->assertSame(true, $config->isLazy('non-sensitive-app', 'non-lazy-key'));
1210
+    }
1211
+
1212
+    public function testUpdateLazyToNotLazy(): void {
1213
+        $config = $this->generateAppConfig();
1214
+        $config->updateLazy('non-sensitive-app', 'lazy-key', false);
1215
+        $this->assertSame(false, $config->isLazy('non-sensitive-app', 'lazy-key'));
1216
+    }
1217
+
1218
+    public function testUpdateLazyToLazyReturnsFalse(): void {
1219
+        $config = $this->generateAppConfig();
1220
+        $this->assertSame(false, $config->updateLazy('non-sensitive-app', 'lazy-key', true));
1221
+    }
1222
+
1223
+    public function testUpdateNotLazyToNotLazyReturnsFalse(): void {
1224
+        $config = $this->generateAppConfig();
1225
+        $this->assertSame(false, $config->updateLazy('non-sensitive-app', 'non-lazy-key', false));
1226
+    }
1227
+
1228
+    public function testUpdateLazyOnUnknownKeyReturnsFalse(): void {
1229
+        $config = $this->generateAppConfig();
1230
+        $this->assertSame(false, $config->updateLazy('non-sensitive-app', 'unknown-key', true));
1231
+    }
1232
+
1233
+    public function testGetDetails(): void {
1234
+        $config = $this->generateAppConfig();
1235
+        $this->assertEquals(
1236
+            [
1237
+                'app' => 'non-sensitive-app',
1238
+                'key' => 'lazy-key',
1239
+                'value' => 'value',
1240
+                'type' => 4,
1241
+                'lazy' => true,
1242
+                'typeString' => 'string',
1243
+                'sensitive' => false,
1244
+            ],
1245
+            $config->getDetails('non-sensitive-app', 'lazy-key')
1246
+        );
1247
+    }
1248
+
1249
+    public function testGetDetailsSensitive(): void {
1250
+        $config = $this->generateAppConfig();
1251
+        $this->assertEquals(
1252
+            [
1253
+                'app' => 'sensitive-app',
1254
+                'key' => 'lazy-key',
1255
+                'value' => 'value',
1256
+                'type' => 4,
1257
+                'lazy' => true,
1258
+                'typeString' => 'string',
1259
+                'sensitive' => true,
1260
+            ],
1261
+            $config->getDetails('sensitive-app', 'lazy-key')
1262
+        );
1263
+    }
1264
+
1265
+    public function testGetDetailsInt(): void {
1266
+        $config = $this->generateAppConfig();
1267
+        $this->assertEquals(
1268
+            [
1269
+                'app' => 'typed',
1270
+                'key' => 'int',
1271
+                'value' => '42',
1272
+                'type' => 8,
1273
+                'lazy' => false,
1274
+                'typeString' => 'integer',
1275
+                'sensitive' => false
1276
+            ],
1277
+            $config->getDetails('typed', 'int')
1278
+        );
1279
+    }
1280
+
1281
+    public function testGetDetailsFloat(): void {
1282
+        $config = $this->generateAppConfig();
1283
+        $this->assertEquals(
1284
+            [
1285
+                'app' => 'typed',
1286
+                'key' => 'float',
1287
+                'value' => '3.14',
1288
+                'type' => 16,
1289
+                'lazy' => false,
1290
+                'typeString' => 'float',
1291
+                'sensitive' => false
1292
+            ],
1293
+            $config->getDetails('typed', 'float')
1294
+        );
1295
+    }
1296
+
1297
+    public function testGetDetailsBool(): void {
1298
+        $config = $this->generateAppConfig();
1299
+        $this->assertEquals(
1300
+            [
1301
+                'app' => 'typed',
1302
+                'key' => 'bool',
1303
+                'value' => '1',
1304
+                'type' => 32,
1305
+                'lazy' => false,
1306
+                'typeString' => 'boolean',
1307
+                'sensitive' => false
1308
+            ],
1309
+            $config->getDetails('typed', 'bool')
1310
+        );
1311
+    }
1312
+
1313
+    public function testGetDetailsArray(): void {
1314
+        $config = $this->generateAppConfig();
1315
+        $this->assertEquals(
1316
+            [
1317
+                'app' => 'typed',
1318
+                'key' => 'array',
1319
+                'value' => '{"test": 1}',
1320
+                'type' => 64,
1321
+                'lazy' => false,
1322
+                'typeString' => 'array',
1323
+                'sensitive' => false
1324
+            ],
1325
+            $config->getDetails('typed', 'array')
1326
+        );
1327
+    }
1328
+
1329
+    public function testDeleteKey(): void {
1330
+        $config = $this->generateAppConfig();
1331
+        $config->deleteKey('anotherapp', 'key');
1332
+        $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1333
+    }
1334
+
1335
+    public function testDeleteKeyCache(): void {
1336
+        $config = $this->generateAppConfig();
1337
+        $config->deleteKey('anotherapp', 'key');
1338
+        $status = $config->statusCache();
1339
+        $this->assertEqualsCanonicalizing(['enabled' => 'no', 'installed_version' => '3.2.1'], $status['fastCache']['anotherapp']);
1340
+    }
1341
+
1342
+    public function testDeleteKeyDatabase(): void {
1343
+        $config = $this->generateAppConfig();
1344
+        $config->deleteKey('anotherapp', 'key');
1345
+        $config->clearCache();
1346
+        $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1347
+    }
1348
+
1349
+    public function testDeleteApp(): void {
1350
+        $config = $this->generateAppConfig();
1351
+        $config->deleteApp('anotherapp');
1352
+        $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1353
+        $this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default'));
1354
+    }
1355
+
1356
+    public function testDeleteAppCache(): void {
1357
+        $config = $this->generateAppConfig();
1358
+        $status = $config->statusCache();
1359
+        $this->assertSame(true, isset($status['fastCache']['anotherapp']));
1360
+        $config->deleteApp('anotherapp');
1361
+        $status = $config->statusCache();
1362
+        $this->assertSame(false, isset($status['fastCache']['anotherapp']));
1363
+    }
1364
+
1365
+    public function testDeleteAppDatabase(): void {
1366
+        $config = $this->generateAppConfig();
1367
+        $config->deleteApp('anotherapp');
1368
+        $config->clearCache();
1369
+        $this->assertSame('default', $config->getValueString('anotherapp', 'key', 'default'));
1370
+        $this->assertSame('default', $config->getValueString('anotherapp', 'enabled', 'default'));
1371
+    }
1372
+
1373
+    public function testClearCache(): void {
1374
+        $config = $this->generateAppConfig();
1375
+        $config->setValueString('feed', 'string', '123454');
1376
+        $config->clearCache();
1377
+        $status = $config->statusCache();
1378
+        $this->assertSame([], $status['fastCache']);
1379
+    }
1380
+
1381
+    public function testSensitiveValuesAreEncrypted(): void {
1382
+        $key = self::getUniqueID('secret');
1383
+
1384
+        $appConfig = $this->generateAppConfig();
1385
+        $secret = md5((string)time());
1386
+        $appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1387
+
1388
+        $this->assertConfigValueNotEquals('testapp', $key, $secret);
1389
+
1390
+        // Can get in same run
1391
+        $actualSecret = $appConfig->getValueString('testapp', $key);
1392
+        $this->assertEquals($secret, $actualSecret);
1393
+
1394
+        // Can get freshly decrypted from DB
1395
+        $newAppConfig = $this->generateAppConfig();
1396
+        $actualSecret = $newAppConfig->getValueString('testapp', $key);
1397
+        $this->assertEquals($secret, $actualSecret);
1398
+    }
1399
+
1400
+    public function testMigratingNonSensitiveValueToSensitiveWithSetValue(): void {
1401
+        $key = self::getUniqueID('secret');
1402
+        $appConfig = $this->generateAppConfig();
1403
+        $secret = sha1((string)time());
1404
+
1405
+        // Unencrypted
1406
+        $appConfig->setValueString('testapp', $key, $secret);
1407
+        $this->assertConfigKey('testapp', $key, $secret);
1408
+
1409
+        // Can get freshly decrypted from DB
1410
+        $newAppConfig = $this->generateAppConfig();
1411
+        $actualSecret = $newAppConfig->getValueString('testapp', $key);
1412
+        $this->assertEquals($secret, $actualSecret);
1413
+
1414
+        // Encrypting on change
1415
+        $appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1416
+        $this->assertConfigValueNotEquals('testapp', $key, $secret);
1417
+
1418
+        // Can get in same run
1419
+        $actualSecret = $appConfig->getValueString('testapp', $key);
1420
+        $this->assertEquals($secret, $actualSecret);
1421
+
1422
+        // Can get freshly decrypted from DB
1423
+        $newAppConfig = $this->generateAppConfig();
1424
+        $actualSecret = $newAppConfig->getValueString('testapp', $key);
1425
+        $this->assertEquals($secret, $actualSecret);
1426
+    }
1427
+
1428
+    public function testUpdateSensitiveValueToNonSensitiveWithUpdateSensitive(): void {
1429
+        $key = self::getUniqueID('secret');
1430
+        $appConfig = $this->generateAppConfig();
1431
+        $secret = sha1((string)time());
1432
+
1433
+        // Encrypted
1434
+        $appConfig->setValueString('testapp', $key, $secret, sensitive: true);
1435
+        $this->assertConfigValueNotEquals('testapp', $key, $secret);
1436
+
1437
+        // Migrate to non-sensitive / non-encrypted
1438
+        $appConfig->updateSensitive('testapp', $key, false);
1439
+        $this->assertConfigKey('testapp', $key, $secret);
1440
+    }
1441
+
1442
+    public function testUpdateNonSensitiveValueToSensitiveWithUpdateSensitive(): void {
1443
+        $key = self::getUniqueID('secret');
1444
+        $appConfig = $this->generateAppConfig();
1445
+        $secret = sha1((string)time());
1446
+
1447
+        // Unencrypted
1448
+        $appConfig->setValueString('testapp', $key, $secret);
1449
+        $this->assertConfigKey('testapp', $key, $secret);
1450
+
1451
+        // Migrate to sensitive / encrypted
1452
+        $appConfig->updateSensitive('testapp', $key, true);
1453
+        $this->assertConfigValueNotEquals('testapp', $key, $secret);
1454
+    }
1455
+
1456
+    protected function loadConfigValueFromDatabase(string $app, string $key): string|false {
1457
+        $sql = $this->connection->getQueryBuilder();
1458
+        $sql->select('configvalue')
1459
+            ->from('appconfig')
1460
+            ->where($sql->expr()->eq('appid', $sql->createParameter('appid')))
1461
+            ->andWhere($sql->expr()->eq('configkey', $sql->createParameter('configkey')))
1462
+            ->setParameter('appid', $app)
1463
+            ->setParameter('configkey', $key);
1464
+        $query = $sql->executeQuery();
1465
+        $actual = $query->fetchOne();
1466
+        $query->closeCursor();
1467
+
1468
+        return $actual;
1469
+    }
1470
+
1471
+    protected function assertConfigKey(string $app, string $key, string|false $expected): void {
1472
+        $this->assertEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1473
+    }
1474
+
1475
+    protected function assertConfigValueNotEquals(string $app, string $key, string|false $expected): void {
1476
+        $this->assertNotEquals($expected, $this->loadConfigValueFromDatabase($app, $key));
1477
+    }
1478 1478
 }
Please login to merge, or discard this patch.
tests/lib/App/AppManagerTest.php 2 patches
Indentation   +862 added lines, -862 removed lines patch added patch discarded remove patch
@@ -36,867 +36,867 @@
 block discarded – undo
36 36
  * @package Test\App
37 37
  */
38 38
 class AppManagerTest extends TestCase {
39
-	/**
40
-	 * @return AppConfig|MockObject
41
-	 */
42
-	protected function getAppConfig() {
43
-		$appConfig = [];
44
-		$config = $this->createMock(AppConfig::class);
45
-
46
-		$config->expects($this->any())
47
-			->method('getValue')
48
-			->willReturnCallback(function ($app, $key, $default) use (&$appConfig) {
49
-				return (isset($appConfig[$app]) and isset($appConfig[$app][$key])) ? $appConfig[$app][$key] : $default;
50
-			});
51
-		$config->expects($this->any())
52
-			->method('setValue')
53
-			->willReturnCallback(function ($app, $key, $value) use (&$appConfig) {
54
-				if (!isset($appConfig[$app])) {
55
-					$appConfig[$app] = [];
56
-				}
57
-				$appConfig[$app][$key] = $value;
58
-			});
59
-		$config->expects($this->any())
60
-			->method('getValues')
61
-			->willReturnCallback(function ($app, $key) use (&$appConfig) {
62
-				if ($app) {
63
-					return $appConfig[$app];
64
-				} else {
65
-					$values = [];
66
-					foreach ($appConfig as $appid => $appData) {
67
-						if (isset($appData[$key])) {
68
-							$values[$appid] = $appData[$key];
69
-						}
70
-					}
71
-					return $values;
72
-				}
73
-			});
74
-		$config->expects($this->any())
75
-			->method('searchValues')
76
-			->willReturnCallback(function ($key, $lazy, $type) use (&$appConfig) {
77
-				$values = [];
78
-				foreach ($appConfig as $appid => $appData) {
79
-					if (isset($appData[$key])) {
80
-						$values[$appid] = $appData[$key];
81
-					}
82
-				}
83
-				return $values;
84
-			});
85
-
86
-		return $config;
87
-	}
88
-
89
-	/** @var IUserSession|MockObject */
90
-	protected $userSession;
91
-
92
-	/** @var IConfig|MockObject */
93
-	private $config;
94
-
95
-	/** @var IGroupManager|MockObject */
96
-	protected $groupManager;
97
-
98
-	/** @var AppConfig|MockObject */
99
-	protected $appConfig;
100
-
101
-	/** @var ICache|MockObject */
102
-	protected $cache;
103
-
104
-	/** @var ICacheFactory|MockObject */
105
-	protected $cacheFactory;
106
-
107
-	/** @var IEventDispatcher|MockObject */
108
-	protected $eventDispatcher;
109
-
110
-	/** @var LoggerInterface|MockObject */
111
-	protected $logger;
112
-
113
-	protected IURLGenerator&MockObject $urlGenerator;
114
-
115
-	protected ServerVersion&MockObject $serverVersion;
116
-
117
-	/** @var IAppManager */
118
-	protected $manager;
119
-
120
-	protected function setUp(): void {
121
-		parent::setUp();
122
-
123
-		$this->userSession = $this->createMock(IUserSession::class);
124
-		$this->groupManager = $this->createMock(IGroupManager::class);
125
-		$this->config = $this->createMock(IConfig::class);
126
-		$this->appConfig = $this->getAppConfig();
127
-		$this->cacheFactory = $this->createMock(ICacheFactory::class);
128
-		$this->cache = $this->createMock(ICache::class);
129
-		$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
130
-		$this->logger = $this->createMock(LoggerInterface::class);
131
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
132
-		$this->serverVersion = $this->createMock(ServerVersion::class);
133
-
134
-		$this->overwriteService(AppConfig::class, $this->appConfig);
135
-		$this->overwriteService(IURLGenerator::class, $this->urlGenerator);
136
-
137
-		$this->cacheFactory->expects($this->any())
138
-			->method('createDistributed')
139
-			->with('settings')
140
-			->willReturn($this->cache);
141
-
142
-		$this->config
143
-			->method('getSystemValueBool')
144
-			->with('installed', false)
145
-			->willReturn(true);
146
-
147
-		$this->manager = new AppManager(
148
-			$this->userSession,
149
-			$this->config,
150
-			$this->groupManager,
151
-			$this->cacheFactory,
152
-			$this->eventDispatcher,
153
-			$this->logger,
154
-			$this->serverVersion,
155
-		);
156
-	}
157
-
158
-	/**
159
-	 * @dataProvider dataGetAppIcon
160
-	 */
161
-	public function testGetAppIcon($callback, ?bool $dark, ?string $expected): void {
162
-		$this->urlGenerator->expects($this->atLeastOnce())
163
-			->method('imagePath')
164
-			->willReturnCallback($callback);
165
-
166
-		if ($dark !== null) {
167
-			$this->assertEquals($expected, $this->manager->getAppIcon('test', $dark));
168
-		} else {
169
-			$this->assertEquals($expected, $this->manager->getAppIcon('test'));
170
-		}
171
-	}
172
-
173
-	public static function dataGetAppIcon(): array {
174
-		$nothing = function ($appId) {
175
-			self::assertEquals('test', $appId);
176
-			throw new \RuntimeException();
177
-		};
178
-
179
-		$createCallback = function ($workingIcons) {
180
-			return function ($appId, $icon) use ($workingIcons) {
181
-				self::assertEquals('test', $appId);
182
-				if (in_array($icon, $workingIcons)) {
183
-					return '/path/' . $icon;
184
-				}
185
-				throw new \RuntimeException();
186
-			};
187
-		};
188
-
189
-		return [
190
-			'does not find anything' => [
191
-				$nothing,
192
-				false,
193
-				null,
194
-			],
195
-			'nothing if request dark but only bright available' => [
196
-				$createCallback(['app.svg']),
197
-				true,
198
-				null,
199
-			],
200
-			'nothing if request bright but only dark available' => [
201
-				$createCallback(['app-dark.svg']),
202
-				false,
203
-				null,
204
-			],
205
-			'bright and only app.svg' => [
206
-				$createCallback(['app.svg']),
207
-				false,
208
-				'/path/app.svg',
209
-			],
210
-			'dark and only app-dark.svg' => [
211
-				$createCallback(['app-dark.svg']),
212
-				true,
213
-				'/path/app-dark.svg',
214
-			],
215
-			'dark only appname -dark.svg' => [
216
-				$createCallback(['test-dark.svg']),
217
-				true,
218
-				'/path/test-dark.svg',
219
-			],
220
-			'bright and only appname.svg' => [
221
-				$createCallback(['test.svg']),
222
-				false,
223
-				'/path/test.svg',
224
-			],
225
-			'priotize custom over default' => [
226
-				$createCallback(['app.svg', 'test.svg']),
227
-				false,
228
-				'/path/test.svg',
229
-			],
230
-			'defaults to bright' => [
231
-				$createCallback(['test-dark.svg', 'test.svg']),
232
-				null,
233
-				'/path/test.svg',
234
-			],
235
-			'no dark icon on default' => [
236
-				$createCallback(['test-dark.svg', 'test.svg', 'app-dark.svg', 'app.svg']),
237
-				false,
238
-				'/path/test.svg',
239
-			],
240
-			'no bright icon on dark' => [
241
-				$createCallback(['test-dark.svg', 'test.svg', 'app-dark.svg', 'app.svg']),
242
-				true,
243
-				'/path/test-dark.svg',
244
-			],
245
-		];
246
-	}
247
-
248
-	public function testEnableApp(): void {
249
-		// making sure "files_trashbin" is disabled
250
-		if ($this->manager->isEnabledForUser('files_trashbin')) {
251
-			$this->manager->disableApp('files_trashbin');
252
-		}
253
-		$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('files_trashbin'));
254
-		$this->manager->enableApp('files_trashbin');
255
-		$this->assertEquals('yes', $this->appConfig->getValue('files_trashbin', 'enabled', 'no'));
256
-	}
257
-
258
-	public function testDisableApp(): void {
259
-		$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppDisableEvent('files_trashbin'));
260
-		$this->manager->disableApp('files_trashbin');
261
-		$this->assertEquals('no', $this->appConfig->getValue('files_trashbin', 'enabled', 'no'));
262
-	}
263
-
264
-	public function testNotEnableIfNotInstalled(): void {
265
-		try {
266
-			$this->manager->enableApp('some_random_name_which_i_hope_is_not_an_app');
267
-			$this->assertFalse(true, 'If this line is reached the expected exception is not thrown.');
268
-		} catch (AppPathNotFoundException $e) {
269
-			// Exception is expected
270
-			$this->assertEquals('Could not find path for some_random_name_which_i_hope_is_not_an_app', $e->getMessage());
271
-		}
272
-
273
-		$this->assertEquals('no', $this->appConfig->getValue(
274
-			'some_random_name_which_i_hope_is_not_an_app', 'enabled', 'no'
275
-		));
276
-	}
277
-
278
-	public function testEnableAppForGroups(): void {
279
-		$group1 = $this->createMock(IGroup::class);
280
-		$group1->method('getGID')
281
-			->willReturn('group1');
282
-		$group2 = $this->createMock(IGroup::class);
283
-		$group2->method('getGID')
284
-			->willReturn('group2');
285
-
286
-		$groups = [$group1, $group2];
287
-
288
-		/** @var AppManager|MockObject $manager */
289
-		$manager = $this->getMockBuilder(AppManager::class)
290
-			->setConstructorArgs([
291
-				$this->userSession,
292
-				$this->config,
293
-				$this->groupManager,
294
-				$this->cacheFactory,
295
-				$this->eventDispatcher,
296
-				$this->logger,
297
-				$this->serverVersion,
298
-			])
299
-			->onlyMethods([
300
-				'getAppPath',
301
-			])
302
-			->getMock();
303
-
304
-		$manager->expects($this->exactly(2))
305
-			->method('getAppPath')
306
-			->with('test')
307
-			->willReturn('apps/test');
308
-
309
-		$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
310
-
311
-		$manager->enableAppForGroups('test', $groups);
312
-		$this->assertEquals('["group1","group2"]', $this->appConfig->getValue('test', 'enabled', 'no'));
313
-	}
314
-
315
-	public static function dataEnableAppForGroupsAllowedTypes(): array {
316
-		return [
317
-			[[]],
318
-			[[
319
-				'types' => [],
320
-			]],
321
-			[[
322
-				'types' => ['nickvergessen'],
323
-			]],
324
-		];
325
-	}
326
-
327
-	/**
328
-	 * @dataProvider dataEnableAppForGroupsAllowedTypes
329
-	 *
330
-	 * @param array $appInfo
331
-	 */
332
-	public function testEnableAppForGroupsAllowedTypes(array $appInfo): void {
333
-		$group1 = $this->createMock(IGroup::class);
334
-		$group1->method('getGID')
335
-			->willReturn('group1');
336
-		$group2 = $this->createMock(IGroup::class);
337
-		$group2->method('getGID')
338
-			->willReturn('group2');
339
-
340
-		$groups = [$group1, $group2];
341
-
342
-		/** @var AppManager|MockObject $manager */
343
-		$manager = $this->getMockBuilder(AppManager::class)
344
-			->setConstructorArgs([
345
-				$this->userSession,
346
-				$this->config,
347
-				$this->groupManager,
348
-				$this->cacheFactory,
349
-				$this->eventDispatcher,
350
-				$this->logger,
351
-				$this->serverVersion,
352
-			])
353
-			->onlyMethods([
354
-				'getAppPath',
355
-				'getAppInfo',
356
-			])
357
-			->getMock();
358
-
359
-		$manager->expects($this->once())
360
-			->method('getAppPath')
361
-			->with('test')
362
-			->willReturn('');
363
-
364
-		$manager->expects($this->once())
365
-			->method('getAppInfo')
366
-			->with('test')
367
-			->willReturn($appInfo);
368
-
369
-		$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
370
-
371
-		$manager->enableAppForGroups('test', $groups);
372
-		$this->assertEquals('["group1","group2"]', $this->appConfig->getValue('test', 'enabled', 'no'));
373
-	}
374
-
375
-	public static function dataEnableAppForGroupsForbiddenTypes(): array {
376
-		return [
377
-			['filesystem'],
378
-			['prelogin'],
379
-			['authentication'],
380
-			['logging'],
381
-			['prevent_group_restriction'],
382
-		];
383
-	}
384
-
385
-	/**
386
-	 * @dataProvider dataEnableAppForGroupsForbiddenTypes
387
-	 *
388
-	 * @param string $type
389
-	 *
390
-	 */
391
-	public function testEnableAppForGroupsForbiddenTypes($type): void {
392
-		$this->expectException(\Exception::class);
393
-		$this->expectExceptionMessage('test can\'t be enabled for groups.');
394
-
395
-		$group1 = $this->createMock(IGroup::class);
396
-		$group1->method('getGID')
397
-			->willReturn('group1');
398
-		$group2 = $this->createMock(IGroup::class);
399
-		$group2->method('getGID')
400
-			->willReturn('group2');
401
-
402
-		$groups = [$group1, $group2];
403
-
404
-		/** @var AppManager|MockObject $manager */
405
-		$manager = $this->getMockBuilder(AppManager::class)
406
-			->setConstructorArgs([
407
-				$this->userSession,
408
-				$this->config,
409
-				$this->groupManager,
410
-				$this->cacheFactory,
411
-				$this->eventDispatcher,
412
-				$this->logger,
413
-				$this->serverVersion,
414
-			])
415
-			->onlyMethods([
416
-				'getAppPath',
417
-				'getAppInfo',
418
-			])
419
-			->getMock();
420
-
421
-		$manager->expects($this->once())
422
-			->method('getAppPath')
423
-			->with('test')
424
-			->willReturn('');
425
-
426
-		$manager->expects($this->once())
427
-			->method('getAppInfo')
428
-			->with('test')
429
-			->willReturn([
430
-				'types' => [$type],
431
-			]);
432
-
433
-		$this->eventDispatcher->expects($this->never())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
434
-
435
-		$manager->enableAppForGroups('test', $groups);
436
-	}
437
-
438
-	public function testIsInstalledEnabled(): void {
439
-		$this->appConfig->setValue('test', 'enabled', 'yes');
440
-		$this->assertTrue($this->manager->isEnabledForAnyone('test'));
441
-	}
442
-
443
-	public function testIsInstalledDisabled(): void {
444
-		$this->appConfig->setValue('test', 'enabled', 'no');
445
-		$this->assertFalse($this->manager->isEnabledForAnyone('test'));
446
-	}
447
-
448
-	public function testIsInstalledEnabledForGroups(): void {
449
-		$this->appConfig->setValue('test', 'enabled', '["foo"]');
450
-		$this->assertTrue($this->manager->isEnabledForAnyone('test'));
451
-	}
452
-
453
-	private function newUser($uid) {
454
-		$user = $this->createMock(IUser::class);
455
-		$user->method('getUID')
456
-			->willReturn($uid);
457
-
458
-		return $user;
459
-	}
460
-
461
-	public function testIsEnabledForUserEnabled(): void {
462
-		$this->appConfig->setValue('test', 'enabled', 'yes');
463
-		$user = $this->newUser('user1');
464
-		$this->assertTrue($this->manager->isEnabledForUser('test', $user));
465
-	}
466
-
467
-	public function testIsEnabledForUserDisabled(): void {
468
-		$this->appConfig->setValue('test', 'enabled', 'no');
469
-		$user = $this->newUser('user1');
470
-		$this->assertFalse($this->manager->isEnabledForUser('test', $user));
471
-	}
472
-
473
-	public function testGetAppPath(): void {
474
-		$this->assertEquals(\OC::$SERVERROOT . '/apps/files', $this->manager->getAppPath('files'));
475
-	}
476
-
477
-	public function testGetAppPathSymlink(): void {
478
-		$fakeAppDirname = sha1(uniqid('test', true));
479
-		$fakeAppPath = sys_get_temp_dir() . '/' . $fakeAppDirname;
480
-		$fakeAppLink = \OC::$SERVERROOT . '/' . $fakeAppDirname;
481
-
482
-		mkdir($fakeAppPath);
483
-		if (symlink($fakeAppPath, $fakeAppLink) === false) {
484
-			$this->markTestSkipped('Failed to create symlink');
485
-		}
486
-
487
-		// Use the symlink as the app path
488
-		\OC::$APPSROOTS[] = [
489
-			'path' => $fakeAppLink,
490
-			'url' => \OC::$WEBROOT . '/' . $fakeAppDirname,
491
-			'writable' => false,
492
-		];
493
-
494
-		$fakeTestAppPath = $fakeAppPath . '/' . 'test-test-app';
495
-		mkdir($fakeTestAppPath);
496
-
497
-		$generatedAppPath = $this->manager->getAppPath('test-test-app');
498
-
499
-		rmdir($fakeTestAppPath);
500
-		unlink($fakeAppLink);
501
-		rmdir($fakeAppPath);
502
-
503
-		$this->assertEquals($fakeAppLink . '/test-test-app', $generatedAppPath);
504
-	}
505
-
506
-	public function testGetAppPathFail(): void {
507
-		$this->expectException(AppPathNotFoundException::class);
508
-		$this->manager->getAppPath('testnotexisting');
509
-	}
510
-
511
-	public function testIsEnabledForUserEnabledForGroup(): void {
512
-		$user = $this->newUser('user1');
513
-		$this->groupManager->expects($this->once())
514
-			->method('getUserGroupIds')
515
-			->with($user)
516
-			->willReturn(['foo', 'bar']);
517
-
518
-		$this->appConfig->setValue('test', 'enabled', '["foo"]');
519
-		$this->assertTrue($this->manager->isEnabledForUser('test', $user));
520
-	}
521
-
522
-	public function testIsEnabledForUserDisabledForGroup(): void {
523
-		$user = $this->newUser('user1');
524
-		$this->groupManager->expects($this->once())
525
-			->method('getUserGroupIds')
526
-			->with($user)
527
-			->willReturn(['bar']);
528
-
529
-		$this->appConfig->setValue('test', 'enabled', '["foo"]');
530
-		$this->assertFalse($this->manager->isEnabledForUser('test', $user));
531
-	}
532
-
533
-	public function testIsEnabledForUserLoggedOut(): void {
534
-		$this->appConfig->setValue('test', 'enabled', '["foo"]');
535
-		$this->assertFalse($this->manager->isEnabledForUser('test'));
536
-	}
537
-
538
-	public function testIsEnabledForUserLoggedIn(): void {
539
-		$user = $this->newUser('user1');
540
-
541
-		$this->userSession->expects($this->once())
542
-			->method('getUser')
543
-			->willReturn($user);
544
-		$this->groupManager->expects($this->once())
545
-			->method('getUserGroupIds')
546
-			->with($user)
547
-			->willReturn(['foo', 'bar']);
548
-
549
-		$this->appConfig->setValue('test', 'enabled', '["foo"]');
550
-		$this->assertTrue($this->manager->isEnabledForUser('test'));
551
-	}
552
-
553
-	public function testGetEnabledApps(): void {
554
-		$this->appConfig->setValue('test1', 'enabled', 'yes');
555
-		$this->appConfig->setValue('test2', 'enabled', 'no');
556
-		$this->appConfig->setValue('test3', 'enabled', '["foo"]');
557
-		$apps = [
558
-			'cloud_federation_api',
559
-			'dav',
560
-			'federatedfilesharing',
561
-			'files',
562
-			'lookup_server_connector',
563
-			'oauth2',
564
-			'profile',
565
-			'provisioning_api',
566
-			'settings',
567
-			'test1',
568
-			'test3',
569
-			'theming',
570
-			'twofactor_backupcodes',
571
-			'viewer',
572
-			'workflowengine',
573
-		];
574
-		$this->assertEquals($apps, $this->manager->getEnabledApps());
575
-	}
576
-
577
-	public function testGetAppsForUser(): void {
578
-		$user = $this->newUser('user1');
579
-		$this->groupManager->expects($this->any())
580
-			->method('getUserGroupIds')
581
-			->with($user)
582
-			->willReturn(['foo', 'bar']);
583
-
584
-		$this->appConfig->setValue('test1', 'enabled', 'yes');
585
-		$this->appConfig->setValue('test2', 'enabled', 'no');
586
-		$this->appConfig->setValue('test3', 'enabled', '["foo"]');
587
-		$this->appConfig->setValue('test4', 'enabled', '["asd"]');
588
-		$enabled = [
589
-			'cloud_federation_api',
590
-			'dav',
591
-			'federatedfilesharing',
592
-			'files',
593
-			'lookup_server_connector',
594
-			'oauth2',
595
-			'profile',
596
-			'provisioning_api',
597
-			'settings',
598
-			'test1',
599
-			'test3',
600
-			'theming',
601
-			'twofactor_backupcodes',
602
-			'viewer',
603
-			'workflowengine',
604
-		];
605
-		$this->assertEquals($enabled, $this->manager->getEnabledAppsForUser($user));
606
-	}
607
-
608
-	public function testGetAppsNeedingUpgrade(): void {
609
-		/** @var AppManager|MockObject $manager */
610
-		$manager = $this->getMockBuilder(AppManager::class)
611
-			->setConstructorArgs([
612
-				$this->userSession,
613
-				$this->config,
614
-				$this->groupManager,
615
-				$this->cacheFactory,
616
-				$this->eventDispatcher,
617
-				$this->logger,
618
-				$this->serverVersion,
619
-			])
620
-			->onlyMethods(['getAppInfo'])
621
-			->getMock();
622
-
623
-		$appInfos = [
624
-			'cloud_federation_api' => ['id' => 'cloud_federation_api'],
625
-			'dav' => ['id' => 'dav'],
626
-			'files' => ['id' => 'files'],
627
-			'federatedfilesharing' => ['id' => 'federatedfilesharing'],
628
-			'profile' => ['id' => 'profile'],
629
-			'provisioning_api' => ['id' => 'provisioning_api'],
630
-			'lookup_server_connector' => ['id' => 'lookup_server_connector'],
631
-			'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '9.0.0'],
632
-			'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'],
633
-			'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
634
-			'test4' => ['id' => 'test4', 'version' => '3.0.0', 'requiremin' => '8.1.0'],
635
-			'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'],
636
-			'settings' => ['id' => 'settings'],
637
-			'theming' => ['id' => 'theming'],
638
-			'twofactor_backupcodes' => ['id' => 'twofactor_backupcodes'],
639
-			'viewer' => ['id' => 'viewer'],
640
-			'workflowengine' => ['id' => 'workflowengine'],
641
-			'oauth2' => ['id' => 'oauth2'],
642
-		];
643
-
644
-		$manager->expects($this->any())
645
-			->method('getAppInfo')
646
-			->willReturnCallback(
647
-				function ($appId) use ($appInfos) {
648
-					return $appInfos[$appId];
649
-				}
650
-			);
651
-
652
-		$this->appConfig->setValue('test1', 'enabled', 'yes');
653
-		$this->appConfig->setValue('test1', 'installed_version', '1.0.0');
654
-		$this->appConfig->setValue('test2', 'enabled', 'yes');
655
-		$this->appConfig->setValue('test2', 'installed_version', '1.0.0');
656
-		$this->appConfig->setValue('test3', 'enabled', 'yes');
657
-		$this->appConfig->setValue('test3', 'installed_version', '1.0.0');
658
-		$this->appConfig->setValue('test4', 'enabled', 'yes');
659
-		$this->appConfig->setValue('test4', 'installed_version', '2.4.0');
660
-
661
-		$apps = $manager->getAppsNeedingUpgrade('8.2.0');
662
-
663
-		$this->assertCount(2, $apps);
664
-		$this->assertEquals('test1', $apps[0]['id']);
665
-		$this->assertEquals('test4', $apps[1]['id']);
666
-	}
667
-
668
-	public function testGetIncompatibleApps(): void {
669
-		/** @var AppManager|MockObject $manager */
670
-		$manager = $this->getMockBuilder(AppManager::class)
671
-			->setConstructorArgs([
672
-				$this->userSession,
673
-				$this->config,
674
-				$this->groupManager,
675
-				$this->cacheFactory,
676
-				$this->eventDispatcher,
677
-				$this->logger,
678
-				$this->serverVersion,
679
-			])
680
-			->onlyMethods(['getAppInfo'])
681
-			->getMock();
682
-
683
-		$appInfos = [
684
-			'cloud_federation_api' => ['id' => 'cloud_federation_api'],
685
-			'dav' => ['id' => 'dav'],
686
-			'files' => ['id' => 'files'],
687
-			'federatedfilesharing' => ['id' => 'federatedfilesharing'],
688
-			'profile' => ['id' => 'profile'],
689
-			'provisioning_api' => ['id' => 'provisioning_api'],
690
-			'lookup_server_connector' => ['id' => 'lookup_server_connector'],
691
-			'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '8.0.0'],
692
-			'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'],
693
-			'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
694
-			'settings' => ['id' => 'settings'],
695
-			'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'],
696
-			'theming' => ['id' => 'theming'],
697
-			'twofactor_backupcodes' => ['id' => 'twofactor_backupcodes'],
698
-			'workflowengine' => ['id' => 'workflowengine'],
699
-			'oauth2' => ['id' => 'oauth2'],
700
-			'viewer' => ['id' => 'viewer'],
701
-		];
702
-
703
-		$manager->expects($this->any())
704
-			->method('getAppInfo')
705
-			->willReturnCallback(
706
-				function ($appId) use ($appInfos) {
707
-					return $appInfos[$appId];
708
-				}
709
-			);
710
-
711
-		$this->appConfig->setValue('test1', 'enabled', 'yes');
712
-		$this->appConfig->setValue('test2', 'enabled', 'yes');
713
-		$this->appConfig->setValue('test3', 'enabled', 'yes');
714
-
715
-		$apps = $manager->getIncompatibleApps('8.2.0');
716
-
717
-		$this->assertCount(2, $apps);
718
-		$this->assertEquals('test1', $apps[0]['id']);
719
-		$this->assertEquals('test3', $apps[1]['id']);
720
-	}
721
-
722
-	public function testGetEnabledAppsForGroup(): void {
723
-		$group = $this->createMock(IGroup::class);
724
-		$group->expects($this->any())
725
-			->method('getGID')
726
-			->willReturn('foo');
727
-
728
-		$this->appConfig->setValue('test1', 'enabled', 'yes');
729
-		$this->appConfig->setValue('test2', 'enabled', 'no');
730
-		$this->appConfig->setValue('test3', 'enabled', '["foo"]');
731
-		$this->appConfig->setValue('test4', 'enabled', '["asd"]');
732
-		$enabled = [
733
-			'cloud_federation_api',
734
-			'dav',
735
-			'federatedfilesharing',
736
-			'files',
737
-			'lookup_server_connector',
738
-			'oauth2',
739
-			'profile',
740
-			'provisioning_api',
741
-			'settings',
742
-			'test1',
743
-			'test3',
744
-			'theming',
745
-			'twofactor_backupcodes',
746
-			'viewer',
747
-			'workflowengine',
748
-		];
749
-		$this->assertEquals($enabled, $this->manager->getEnabledAppsForGroup($group));
750
-	}
751
-
752
-	public function testGetAppRestriction(): void {
753
-		$this->appConfig->setValue('test1', 'enabled', 'yes');
754
-		$this->appConfig->setValue('test2', 'enabled', 'no');
755
-		$this->appConfig->setValue('test3', 'enabled', '["foo"]');
756
-
757
-		$this->assertEquals([], $this->manager->getAppRestriction('test1'));
758
-		$this->assertEquals([], $this->manager->getAppRestriction('test2'));
759
-		$this->assertEquals(['foo'], $this->manager->getAppRestriction('test3'));
760
-	}
761
-
762
-	public static function isBackendRequiredDataProvider(): array {
763
-		return [
764
-			// backend available
765
-			[
766
-				'caldav',
767
-				['app1' => ['caldav']],
768
-				true,
769
-			],
770
-			[
771
-				'caldav',
772
-				['app1' => [], 'app2' => ['foo'], 'app3' => ['caldav']],
773
-				true,
774
-			],
775
-			// backend not available
776
-			[
777
-				'caldav',
778
-				['app3' => [], 'app1' => ['foo'], 'app2' => ['bar', 'baz']],
779
-				false,
780
-			],
781
-			// no app available
782
-			[
783
-				'caldav',
784
-				[],
785
-				false,
786
-			],
787
-		];
788
-	}
789
-
790
-	/**
791
-	 * @dataProvider isBackendRequiredDataProvider
792
-	 */
793
-	public function testIsBackendRequired(
794
-		string $backend,
795
-		array $appBackends,
796
-		bool $expected,
797
-	): void {
798
-		$appInfoData = array_map(
799
-			static fn (array $backends) => ['dependencies' => ['backend' => $backends]],
800
-			$appBackends,
801
-		);
802
-
803
-		$reflection = new \ReflectionClass($this->manager);
804
-		$property = $reflection->getProperty('appInfos');
805
-		$property->setValue($this->manager, $appInfoData);
806
-
807
-		$this->assertEquals($expected, $this->manager->isBackendRequired($backend));
808
-	}
809
-
810
-	public function testGetAppVersion() {
811
-		$manager = $this->getMockBuilder(AppManager::class)
812
-			->setConstructorArgs([
813
-				$this->userSession,
814
-				$this->config,
815
-				$this->groupManager,
816
-				$this->cacheFactory,
817
-				$this->eventDispatcher,
818
-				$this->logger,
819
-				$this->serverVersion,
820
-			])
821
-			->onlyMethods([
822
-				'getAppInfo',
823
-			])
824
-			->getMock();
825
-
826
-		$manager->expects(self::once())
827
-			->method('getAppInfo')
828
-			->with('myapp')
829
-			->willReturn(['version' => '99.99.99-rc.99']);
830
-
831
-		$this->serverVersion
832
-			->expects(self::never())
833
-			->method('getVersionString');
834
-
835
-		$this->assertEquals(
836
-			'99.99.99-rc.99',
837
-			$manager->getAppVersion('myapp'),
838
-		);
839
-	}
840
-
841
-	public function testGetAppVersionCore() {
842
-		$manager = $this->getMockBuilder(AppManager::class)
843
-			->setConstructorArgs([
844
-				$this->userSession,
845
-				$this->config,
846
-				$this->groupManager,
847
-				$this->cacheFactory,
848
-				$this->eventDispatcher,
849
-				$this->logger,
850
-				$this->serverVersion,
851
-			])
852
-			->onlyMethods([
853
-				'getAppInfo',
854
-			])
855
-			->getMock();
856
-
857
-		$manager->expects(self::never())
858
-			->method('getAppInfo');
859
-
860
-		$this->serverVersion
861
-			->expects(self::once())
862
-			->method('getVersionString')
863
-			->willReturn('1.2.3-beta.4');
864
-
865
-		$this->assertEquals(
866
-			'1.2.3-beta.4',
867
-			$manager->getAppVersion('core'),
868
-		);
869
-	}
870
-
871
-	public function testGetAppVersionUnknown() {
872
-		$manager = $this->getMockBuilder(AppManager::class)
873
-			->setConstructorArgs([
874
-				$this->userSession,
875
-				$this->config,
876
-				$this->groupManager,
877
-				$this->cacheFactory,
878
-				$this->eventDispatcher,
879
-				$this->logger,
880
-				$this->serverVersion,
881
-			])
882
-			->onlyMethods([
883
-				'getAppInfo',
884
-			])
885
-			->getMock();
886
-
887
-		$manager->expects(self::once())
888
-			->method('getAppInfo')
889
-			->with('unknown')
890
-			->willReturn(null);
891
-
892
-		$this->serverVersion
893
-			->expects(self::never())
894
-			->method('getVersionString');
895
-
896
-		$this->assertEquals(
897
-			'0',
898
-			$manager->getAppVersion('unknown'),
899
-		);
900
-	}
39
+    /**
40
+     * @return AppConfig|MockObject
41
+     */
42
+    protected function getAppConfig() {
43
+        $appConfig = [];
44
+        $config = $this->createMock(AppConfig::class);
45
+
46
+        $config->expects($this->any())
47
+            ->method('getValue')
48
+            ->willReturnCallback(function ($app, $key, $default) use (&$appConfig) {
49
+                return (isset($appConfig[$app]) and isset($appConfig[$app][$key])) ? $appConfig[$app][$key] : $default;
50
+            });
51
+        $config->expects($this->any())
52
+            ->method('setValue')
53
+            ->willReturnCallback(function ($app, $key, $value) use (&$appConfig) {
54
+                if (!isset($appConfig[$app])) {
55
+                    $appConfig[$app] = [];
56
+                }
57
+                $appConfig[$app][$key] = $value;
58
+            });
59
+        $config->expects($this->any())
60
+            ->method('getValues')
61
+            ->willReturnCallback(function ($app, $key) use (&$appConfig) {
62
+                if ($app) {
63
+                    return $appConfig[$app];
64
+                } else {
65
+                    $values = [];
66
+                    foreach ($appConfig as $appid => $appData) {
67
+                        if (isset($appData[$key])) {
68
+                            $values[$appid] = $appData[$key];
69
+                        }
70
+                    }
71
+                    return $values;
72
+                }
73
+            });
74
+        $config->expects($this->any())
75
+            ->method('searchValues')
76
+            ->willReturnCallback(function ($key, $lazy, $type) use (&$appConfig) {
77
+                $values = [];
78
+                foreach ($appConfig as $appid => $appData) {
79
+                    if (isset($appData[$key])) {
80
+                        $values[$appid] = $appData[$key];
81
+                    }
82
+                }
83
+                return $values;
84
+            });
85
+
86
+        return $config;
87
+    }
88
+
89
+    /** @var IUserSession|MockObject */
90
+    protected $userSession;
91
+
92
+    /** @var IConfig|MockObject */
93
+    private $config;
94
+
95
+    /** @var IGroupManager|MockObject */
96
+    protected $groupManager;
97
+
98
+    /** @var AppConfig|MockObject */
99
+    protected $appConfig;
100
+
101
+    /** @var ICache|MockObject */
102
+    protected $cache;
103
+
104
+    /** @var ICacheFactory|MockObject */
105
+    protected $cacheFactory;
106
+
107
+    /** @var IEventDispatcher|MockObject */
108
+    protected $eventDispatcher;
109
+
110
+    /** @var LoggerInterface|MockObject */
111
+    protected $logger;
112
+
113
+    protected IURLGenerator&MockObject $urlGenerator;
114
+
115
+    protected ServerVersion&MockObject $serverVersion;
116
+
117
+    /** @var IAppManager */
118
+    protected $manager;
119
+
120
+    protected function setUp(): void {
121
+        parent::setUp();
122
+
123
+        $this->userSession = $this->createMock(IUserSession::class);
124
+        $this->groupManager = $this->createMock(IGroupManager::class);
125
+        $this->config = $this->createMock(IConfig::class);
126
+        $this->appConfig = $this->getAppConfig();
127
+        $this->cacheFactory = $this->createMock(ICacheFactory::class);
128
+        $this->cache = $this->createMock(ICache::class);
129
+        $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
130
+        $this->logger = $this->createMock(LoggerInterface::class);
131
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
132
+        $this->serverVersion = $this->createMock(ServerVersion::class);
133
+
134
+        $this->overwriteService(AppConfig::class, $this->appConfig);
135
+        $this->overwriteService(IURLGenerator::class, $this->urlGenerator);
136
+
137
+        $this->cacheFactory->expects($this->any())
138
+            ->method('createDistributed')
139
+            ->with('settings')
140
+            ->willReturn($this->cache);
141
+
142
+        $this->config
143
+            ->method('getSystemValueBool')
144
+            ->with('installed', false)
145
+            ->willReturn(true);
146
+
147
+        $this->manager = new AppManager(
148
+            $this->userSession,
149
+            $this->config,
150
+            $this->groupManager,
151
+            $this->cacheFactory,
152
+            $this->eventDispatcher,
153
+            $this->logger,
154
+            $this->serverVersion,
155
+        );
156
+    }
157
+
158
+    /**
159
+     * @dataProvider dataGetAppIcon
160
+     */
161
+    public function testGetAppIcon($callback, ?bool $dark, ?string $expected): void {
162
+        $this->urlGenerator->expects($this->atLeastOnce())
163
+            ->method('imagePath')
164
+            ->willReturnCallback($callback);
165
+
166
+        if ($dark !== null) {
167
+            $this->assertEquals($expected, $this->manager->getAppIcon('test', $dark));
168
+        } else {
169
+            $this->assertEquals($expected, $this->manager->getAppIcon('test'));
170
+        }
171
+    }
172
+
173
+    public static function dataGetAppIcon(): array {
174
+        $nothing = function ($appId) {
175
+            self::assertEquals('test', $appId);
176
+            throw new \RuntimeException();
177
+        };
178
+
179
+        $createCallback = function ($workingIcons) {
180
+            return function ($appId, $icon) use ($workingIcons) {
181
+                self::assertEquals('test', $appId);
182
+                if (in_array($icon, $workingIcons)) {
183
+                    return '/path/' . $icon;
184
+                }
185
+                throw new \RuntimeException();
186
+            };
187
+        };
188
+
189
+        return [
190
+            'does not find anything' => [
191
+                $nothing,
192
+                false,
193
+                null,
194
+            ],
195
+            'nothing if request dark but only bright available' => [
196
+                $createCallback(['app.svg']),
197
+                true,
198
+                null,
199
+            ],
200
+            'nothing if request bright but only dark available' => [
201
+                $createCallback(['app-dark.svg']),
202
+                false,
203
+                null,
204
+            ],
205
+            'bright and only app.svg' => [
206
+                $createCallback(['app.svg']),
207
+                false,
208
+                '/path/app.svg',
209
+            ],
210
+            'dark and only app-dark.svg' => [
211
+                $createCallback(['app-dark.svg']),
212
+                true,
213
+                '/path/app-dark.svg',
214
+            ],
215
+            'dark only appname -dark.svg' => [
216
+                $createCallback(['test-dark.svg']),
217
+                true,
218
+                '/path/test-dark.svg',
219
+            ],
220
+            'bright and only appname.svg' => [
221
+                $createCallback(['test.svg']),
222
+                false,
223
+                '/path/test.svg',
224
+            ],
225
+            'priotize custom over default' => [
226
+                $createCallback(['app.svg', 'test.svg']),
227
+                false,
228
+                '/path/test.svg',
229
+            ],
230
+            'defaults to bright' => [
231
+                $createCallback(['test-dark.svg', 'test.svg']),
232
+                null,
233
+                '/path/test.svg',
234
+            ],
235
+            'no dark icon on default' => [
236
+                $createCallback(['test-dark.svg', 'test.svg', 'app-dark.svg', 'app.svg']),
237
+                false,
238
+                '/path/test.svg',
239
+            ],
240
+            'no bright icon on dark' => [
241
+                $createCallback(['test-dark.svg', 'test.svg', 'app-dark.svg', 'app.svg']),
242
+                true,
243
+                '/path/test-dark.svg',
244
+            ],
245
+        ];
246
+    }
247
+
248
+    public function testEnableApp(): void {
249
+        // making sure "files_trashbin" is disabled
250
+        if ($this->manager->isEnabledForUser('files_trashbin')) {
251
+            $this->manager->disableApp('files_trashbin');
252
+        }
253
+        $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('files_trashbin'));
254
+        $this->manager->enableApp('files_trashbin');
255
+        $this->assertEquals('yes', $this->appConfig->getValue('files_trashbin', 'enabled', 'no'));
256
+    }
257
+
258
+    public function testDisableApp(): void {
259
+        $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppDisableEvent('files_trashbin'));
260
+        $this->manager->disableApp('files_trashbin');
261
+        $this->assertEquals('no', $this->appConfig->getValue('files_trashbin', 'enabled', 'no'));
262
+    }
263
+
264
+    public function testNotEnableIfNotInstalled(): void {
265
+        try {
266
+            $this->manager->enableApp('some_random_name_which_i_hope_is_not_an_app');
267
+            $this->assertFalse(true, 'If this line is reached the expected exception is not thrown.');
268
+        } catch (AppPathNotFoundException $e) {
269
+            // Exception is expected
270
+            $this->assertEquals('Could not find path for some_random_name_which_i_hope_is_not_an_app', $e->getMessage());
271
+        }
272
+
273
+        $this->assertEquals('no', $this->appConfig->getValue(
274
+            'some_random_name_which_i_hope_is_not_an_app', 'enabled', 'no'
275
+        ));
276
+    }
277
+
278
+    public function testEnableAppForGroups(): void {
279
+        $group1 = $this->createMock(IGroup::class);
280
+        $group1->method('getGID')
281
+            ->willReturn('group1');
282
+        $group2 = $this->createMock(IGroup::class);
283
+        $group2->method('getGID')
284
+            ->willReturn('group2');
285
+
286
+        $groups = [$group1, $group2];
287
+
288
+        /** @var AppManager|MockObject $manager */
289
+        $manager = $this->getMockBuilder(AppManager::class)
290
+            ->setConstructorArgs([
291
+                $this->userSession,
292
+                $this->config,
293
+                $this->groupManager,
294
+                $this->cacheFactory,
295
+                $this->eventDispatcher,
296
+                $this->logger,
297
+                $this->serverVersion,
298
+            ])
299
+            ->onlyMethods([
300
+                'getAppPath',
301
+            ])
302
+            ->getMock();
303
+
304
+        $manager->expects($this->exactly(2))
305
+            ->method('getAppPath')
306
+            ->with('test')
307
+            ->willReturn('apps/test');
308
+
309
+        $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
310
+
311
+        $manager->enableAppForGroups('test', $groups);
312
+        $this->assertEquals('["group1","group2"]', $this->appConfig->getValue('test', 'enabled', 'no'));
313
+    }
314
+
315
+    public static function dataEnableAppForGroupsAllowedTypes(): array {
316
+        return [
317
+            [[]],
318
+            [[
319
+                'types' => [],
320
+            ]],
321
+            [[
322
+                'types' => ['nickvergessen'],
323
+            ]],
324
+        ];
325
+    }
326
+
327
+    /**
328
+     * @dataProvider dataEnableAppForGroupsAllowedTypes
329
+     *
330
+     * @param array $appInfo
331
+     */
332
+    public function testEnableAppForGroupsAllowedTypes(array $appInfo): void {
333
+        $group1 = $this->createMock(IGroup::class);
334
+        $group1->method('getGID')
335
+            ->willReturn('group1');
336
+        $group2 = $this->createMock(IGroup::class);
337
+        $group2->method('getGID')
338
+            ->willReturn('group2');
339
+
340
+        $groups = [$group1, $group2];
341
+
342
+        /** @var AppManager|MockObject $manager */
343
+        $manager = $this->getMockBuilder(AppManager::class)
344
+            ->setConstructorArgs([
345
+                $this->userSession,
346
+                $this->config,
347
+                $this->groupManager,
348
+                $this->cacheFactory,
349
+                $this->eventDispatcher,
350
+                $this->logger,
351
+                $this->serverVersion,
352
+            ])
353
+            ->onlyMethods([
354
+                'getAppPath',
355
+                'getAppInfo',
356
+            ])
357
+            ->getMock();
358
+
359
+        $manager->expects($this->once())
360
+            ->method('getAppPath')
361
+            ->with('test')
362
+            ->willReturn('');
363
+
364
+        $manager->expects($this->once())
365
+            ->method('getAppInfo')
366
+            ->with('test')
367
+            ->willReturn($appInfo);
368
+
369
+        $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
370
+
371
+        $manager->enableAppForGroups('test', $groups);
372
+        $this->assertEquals('["group1","group2"]', $this->appConfig->getValue('test', 'enabled', 'no'));
373
+    }
374
+
375
+    public static function dataEnableAppForGroupsForbiddenTypes(): array {
376
+        return [
377
+            ['filesystem'],
378
+            ['prelogin'],
379
+            ['authentication'],
380
+            ['logging'],
381
+            ['prevent_group_restriction'],
382
+        ];
383
+    }
384
+
385
+    /**
386
+     * @dataProvider dataEnableAppForGroupsForbiddenTypes
387
+     *
388
+     * @param string $type
389
+     *
390
+     */
391
+    public function testEnableAppForGroupsForbiddenTypes($type): void {
392
+        $this->expectException(\Exception::class);
393
+        $this->expectExceptionMessage('test can\'t be enabled for groups.');
394
+
395
+        $group1 = $this->createMock(IGroup::class);
396
+        $group1->method('getGID')
397
+            ->willReturn('group1');
398
+        $group2 = $this->createMock(IGroup::class);
399
+        $group2->method('getGID')
400
+            ->willReturn('group2');
401
+
402
+        $groups = [$group1, $group2];
403
+
404
+        /** @var AppManager|MockObject $manager */
405
+        $manager = $this->getMockBuilder(AppManager::class)
406
+            ->setConstructorArgs([
407
+                $this->userSession,
408
+                $this->config,
409
+                $this->groupManager,
410
+                $this->cacheFactory,
411
+                $this->eventDispatcher,
412
+                $this->logger,
413
+                $this->serverVersion,
414
+            ])
415
+            ->onlyMethods([
416
+                'getAppPath',
417
+                'getAppInfo',
418
+            ])
419
+            ->getMock();
420
+
421
+        $manager->expects($this->once())
422
+            ->method('getAppPath')
423
+            ->with('test')
424
+            ->willReturn('');
425
+
426
+        $manager->expects($this->once())
427
+            ->method('getAppInfo')
428
+            ->with('test')
429
+            ->willReturn([
430
+                'types' => [$type],
431
+            ]);
432
+
433
+        $this->eventDispatcher->expects($this->never())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
434
+
435
+        $manager->enableAppForGroups('test', $groups);
436
+    }
437
+
438
+    public function testIsInstalledEnabled(): void {
439
+        $this->appConfig->setValue('test', 'enabled', 'yes');
440
+        $this->assertTrue($this->manager->isEnabledForAnyone('test'));
441
+    }
442
+
443
+    public function testIsInstalledDisabled(): void {
444
+        $this->appConfig->setValue('test', 'enabled', 'no');
445
+        $this->assertFalse($this->manager->isEnabledForAnyone('test'));
446
+    }
447
+
448
+    public function testIsInstalledEnabledForGroups(): void {
449
+        $this->appConfig->setValue('test', 'enabled', '["foo"]');
450
+        $this->assertTrue($this->manager->isEnabledForAnyone('test'));
451
+    }
452
+
453
+    private function newUser($uid) {
454
+        $user = $this->createMock(IUser::class);
455
+        $user->method('getUID')
456
+            ->willReturn($uid);
457
+
458
+        return $user;
459
+    }
460
+
461
+    public function testIsEnabledForUserEnabled(): void {
462
+        $this->appConfig->setValue('test', 'enabled', 'yes');
463
+        $user = $this->newUser('user1');
464
+        $this->assertTrue($this->manager->isEnabledForUser('test', $user));
465
+    }
466
+
467
+    public function testIsEnabledForUserDisabled(): void {
468
+        $this->appConfig->setValue('test', 'enabled', 'no');
469
+        $user = $this->newUser('user1');
470
+        $this->assertFalse($this->manager->isEnabledForUser('test', $user));
471
+    }
472
+
473
+    public function testGetAppPath(): void {
474
+        $this->assertEquals(\OC::$SERVERROOT . '/apps/files', $this->manager->getAppPath('files'));
475
+    }
476
+
477
+    public function testGetAppPathSymlink(): void {
478
+        $fakeAppDirname = sha1(uniqid('test', true));
479
+        $fakeAppPath = sys_get_temp_dir() . '/' . $fakeAppDirname;
480
+        $fakeAppLink = \OC::$SERVERROOT . '/' . $fakeAppDirname;
481
+
482
+        mkdir($fakeAppPath);
483
+        if (symlink($fakeAppPath, $fakeAppLink) === false) {
484
+            $this->markTestSkipped('Failed to create symlink');
485
+        }
486
+
487
+        // Use the symlink as the app path
488
+        \OC::$APPSROOTS[] = [
489
+            'path' => $fakeAppLink,
490
+            'url' => \OC::$WEBROOT . '/' . $fakeAppDirname,
491
+            'writable' => false,
492
+        ];
493
+
494
+        $fakeTestAppPath = $fakeAppPath . '/' . 'test-test-app';
495
+        mkdir($fakeTestAppPath);
496
+
497
+        $generatedAppPath = $this->manager->getAppPath('test-test-app');
498
+
499
+        rmdir($fakeTestAppPath);
500
+        unlink($fakeAppLink);
501
+        rmdir($fakeAppPath);
502
+
503
+        $this->assertEquals($fakeAppLink . '/test-test-app', $generatedAppPath);
504
+    }
505
+
506
+    public function testGetAppPathFail(): void {
507
+        $this->expectException(AppPathNotFoundException::class);
508
+        $this->manager->getAppPath('testnotexisting');
509
+    }
510
+
511
+    public function testIsEnabledForUserEnabledForGroup(): void {
512
+        $user = $this->newUser('user1');
513
+        $this->groupManager->expects($this->once())
514
+            ->method('getUserGroupIds')
515
+            ->with($user)
516
+            ->willReturn(['foo', 'bar']);
517
+
518
+        $this->appConfig->setValue('test', 'enabled', '["foo"]');
519
+        $this->assertTrue($this->manager->isEnabledForUser('test', $user));
520
+    }
521
+
522
+    public function testIsEnabledForUserDisabledForGroup(): void {
523
+        $user = $this->newUser('user1');
524
+        $this->groupManager->expects($this->once())
525
+            ->method('getUserGroupIds')
526
+            ->with($user)
527
+            ->willReturn(['bar']);
528
+
529
+        $this->appConfig->setValue('test', 'enabled', '["foo"]');
530
+        $this->assertFalse($this->manager->isEnabledForUser('test', $user));
531
+    }
532
+
533
+    public function testIsEnabledForUserLoggedOut(): void {
534
+        $this->appConfig->setValue('test', 'enabled', '["foo"]');
535
+        $this->assertFalse($this->manager->isEnabledForUser('test'));
536
+    }
537
+
538
+    public function testIsEnabledForUserLoggedIn(): void {
539
+        $user = $this->newUser('user1');
540
+
541
+        $this->userSession->expects($this->once())
542
+            ->method('getUser')
543
+            ->willReturn($user);
544
+        $this->groupManager->expects($this->once())
545
+            ->method('getUserGroupIds')
546
+            ->with($user)
547
+            ->willReturn(['foo', 'bar']);
548
+
549
+        $this->appConfig->setValue('test', 'enabled', '["foo"]');
550
+        $this->assertTrue($this->manager->isEnabledForUser('test'));
551
+    }
552
+
553
+    public function testGetEnabledApps(): void {
554
+        $this->appConfig->setValue('test1', 'enabled', 'yes');
555
+        $this->appConfig->setValue('test2', 'enabled', 'no');
556
+        $this->appConfig->setValue('test3', 'enabled', '["foo"]');
557
+        $apps = [
558
+            'cloud_federation_api',
559
+            'dav',
560
+            'federatedfilesharing',
561
+            'files',
562
+            'lookup_server_connector',
563
+            'oauth2',
564
+            'profile',
565
+            'provisioning_api',
566
+            'settings',
567
+            'test1',
568
+            'test3',
569
+            'theming',
570
+            'twofactor_backupcodes',
571
+            'viewer',
572
+            'workflowengine',
573
+        ];
574
+        $this->assertEquals($apps, $this->manager->getEnabledApps());
575
+    }
576
+
577
+    public function testGetAppsForUser(): void {
578
+        $user = $this->newUser('user1');
579
+        $this->groupManager->expects($this->any())
580
+            ->method('getUserGroupIds')
581
+            ->with($user)
582
+            ->willReturn(['foo', 'bar']);
583
+
584
+        $this->appConfig->setValue('test1', 'enabled', 'yes');
585
+        $this->appConfig->setValue('test2', 'enabled', 'no');
586
+        $this->appConfig->setValue('test3', 'enabled', '["foo"]');
587
+        $this->appConfig->setValue('test4', 'enabled', '["asd"]');
588
+        $enabled = [
589
+            'cloud_federation_api',
590
+            'dav',
591
+            'federatedfilesharing',
592
+            'files',
593
+            'lookup_server_connector',
594
+            'oauth2',
595
+            'profile',
596
+            'provisioning_api',
597
+            'settings',
598
+            'test1',
599
+            'test3',
600
+            'theming',
601
+            'twofactor_backupcodes',
602
+            'viewer',
603
+            'workflowengine',
604
+        ];
605
+        $this->assertEquals($enabled, $this->manager->getEnabledAppsForUser($user));
606
+    }
607
+
608
+    public function testGetAppsNeedingUpgrade(): void {
609
+        /** @var AppManager|MockObject $manager */
610
+        $manager = $this->getMockBuilder(AppManager::class)
611
+            ->setConstructorArgs([
612
+                $this->userSession,
613
+                $this->config,
614
+                $this->groupManager,
615
+                $this->cacheFactory,
616
+                $this->eventDispatcher,
617
+                $this->logger,
618
+                $this->serverVersion,
619
+            ])
620
+            ->onlyMethods(['getAppInfo'])
621
+            ->getMock();
622
+
623
+        $appInfos = [
624
+            'cloud_federation_api' => ['id' => 'cloud_federation_api'],
625
+            'dav' => ['id' => 'dav'],
626
+            'files' => ['id' => 'files'],
627
+            'federatedfilesharing' => ['id' => 'federatedfilesharing'],
628
+            'profile' => ['id' => 'profile'],
629
+            'provisioning_api' => ['id' => 'provisioning_api'],
630
+            'lookup_server_connector' => ['id' => 'lookup_server_connector'],
631
+            'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '9.0.0'],
632
+            'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'],
633
+            'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
634
+            'test4' => ['id' => 'test4', 'version' => '3.0.0', 'requiremin' => '8.1.0'],
635
+            'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'],
636
+            'settings' => ['id' => 'settings'],
637
+            'theming' => ['id' => 'theming'],
638
+            'twofactor_backupcodes' => ['id' => 'twofactor_backupcodes'],
639
+            'viewer' => ['id' => 'viewer'],
640
+            'workflowengine' => ['id' => 'workflowengine'],
641
+            'oauth2' => ['id' => 'oauth2'],
642
+        ];
643
+
644
+        $manager->expects($this->any())
645
+            ->method('getAppInfo')
646
+            ->willReturnCallback(
647
+                function ($appId) use ($appInfos) {
648
+                    return $appInfos[$appId];
649
+                }
650
+            );
651
+
652
+        $this->appConfig->setValue('test1', 'enabled', 'yes');
653
+        $this->appConfig->setValue('test1', 'installed_version', '1.0.0');
654
+        $this->appConfig->setValue('test2', 'enabled', 'yes');
655
+        $this->appConfig->setValue('test2', 'installed_version', '1.0.0');
656
+        $this->appConfig->setValue('test3', 'enabled', 'yes');
657
+        $this->appConfig->setValue('test3', 'installed_version', '1.0.0');
658
+        $this->appConfig->setValue('test4', 'enabled', 'yes');
659
+        $this->appConfig->setValue('test4', 'installed_version', '2.4.0');
660
+
661
+        $apps = $manager->getAppsNeedingUpgrade('8.2.0');
662
+
663
+        $this->assertCount(2, $apps);
664
+        $this->assertEquals('test1', $apps[0]['id']);
665
+        $this->assertEquals('test4', $apps[1]['id']);
666
+    }
667
+
668
+    public function testGetIncompatibleApps(): void {
669
+        /** @var AppManager|MockObject $manager */
670
+        $manager = $this->getMockBuilder(AppManager::class)
671
+            ->setConstructorArgs([
672
+                $this->userSession,
673
+                $this->config,
674
+                $this->groupManager,
675
+                $this->cacheFactory,
676
+                $this->eventDispatcher,
677
+                $this->logger,
678
+                $this->serverVersion,
679
+            ])
680
+            ->onlyMethods(['getAppInfo'])
681
+            ->getMock();
682
+
683
+        $appInfos = [
684
+            'cloud_federation_api' => ['id' => 'cloud_federation_api'],
685
+            'dav' => ['id' => 'dav'],
686
+            'files' => ['id' => 'files'],
687
+            'federatedfilesharing' => ['id' => 'federatedfilesharing'],
688
+            'profile' => ['id' => 'profile'],
689
+            'provisioning_api' => ['id' => 'provisioning_api'],
690
+            'lookup_server_connector' => ['id' => 'lookup_server_connector'],
691
+            'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '8.0.0'],
692
+            'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'],
693
+            'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
694
+            'settings' => ['id' => 'settings'],
695
+            'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'],
696
+            'theming' => ['id' => 'theming'],
697
+            'twofactor_backupcodes' => ['id' => 'twofactor_backupcodes'],
698
+            'workflowengine' => ['id' => 'workflowengine'],
699
+            'oauth2' => ['id' => 'oauth2'],
700
+            'viewer' => ['id' => 'viewer'],
701
+        ];
702
+
703
+        $manager->expects($this->any())
704
+            ->method('getAppInfo')
705
+            ->willReturnCallback(
706
+                function ($appId) use ($appInfos) {
707
+                    return $appInfos[$appId];
708
+                }
709
+            );
710
+
711
+        $this->appConfig->setValue('test1', 'enabled', 'yes');
712
+        $this->appConfig->setValue('test2', 'enabled', 'yes');
713
+        $this->appConfig->setValue('test3', 'enabled', 'yes');
714
+
715
+        $apps = $manager->getIncompatibleApps('8.2.0');
716
+
717
+        $this->assertCount(2, $apps);
718
+        $this->assertEquals('test1', $apps[0]['id']);
719
+        $this->assertEquals('test3', $apps[1]['id']);
720
+    }
721
+
722
+    public function testGetEnabledAppsForGroup(): void {
723
+        $group = $this->createMock(IGroup::class);
724
+        $group->expects($this->any())
725
+            ->method('getGID')
726
+            ->willReturn('foo');
727
+
728
+        $this->appConfig->setValue('test1', 'enabled', 'yes');
729
+        $this->appConfig->setValue('test2', 'enabled', 'no');
730
+        $this->appConfig->setValue('test3', 'enabled', '["foo"]');
731
+        $this->appConfig->setValue('test4', 'enabled', '["asd"]');
732
+        $enabled = [
733
+            'cloud_federation_api',
734
+            'dav',
735
+            'federatedfilesharing',
736
+            'files',
737
+            'lookup_server_connector',
738
+            'oauth2',
739
+            'profile',
740
+            'provisioning_api',
741
+            'settings',
742
+            'test1',
743
+            'test3',
744
+            'theming',
745
+            'twofactor_backupcodes',
746
+            'viewer',
747
+            'workflowengine',
748
+        ];
749
+        $this->assertEquals($enabled, $this->manager->getEnabledAppsForGroup($group));
750
+    }
751
+
752
+    public function testGetAppRestriction(): void {
753
+        $this->appConfig->setValue('test1', 'enabled', 'yes');
754
+        $this->appConfig->setValue('test2', 'enabled', 'no');
755
+        $this->appConfig->setValue('test3', 'enabled', '["foo"]');
756
+
757
+        $this->assertEquals([], $this->manager->getAppRestriction('test1'));
758
+        $this->assertEquals([], $this->manager->getAppRestriction('test2'));
759
+        $this->assertEquals(['foo'], $this->manager->getAppRestriction('test3'));
760
+    }
761
+
762
+    public static function isBackendRequiredDataProvider(): array {
763
+        return [
764
+            // backend available
765
+            [
766
+                'caldav',
767
+                ['app1' => ['caldav']],
768
+                true,
769
+            ],
770
+            [
771
+                'caldav',
772
+                ['app1' => [], 'app2' => ['foo'], 'app3' => ['caldav']],
773
+                true,
774
+            ],
775
+            // backend not available
776
+            [
777
+                'caldav',
778
+                ['app3' => [], 'app1' => ['foo'], 'app2' => ['bar', 'baz']],
779
+                false,
780
+            ],
781
+            // no app available
782
+            [
783
+                'caldav',
784
+                [],
785
+                false,
786
+            ],
787
+        ];
788
+    }
789
+
790
+    /**
791
+     * @dataProvider isBackendRequiredDataProvider
792
+     */
793
+    public function testIsBackendRequired(
794
+        string $backend,
795
+        array $appBackends,
796
+        bool $expected,
797
+    ): void {
798
+        $appInfoData = array_map(
799
+            static fn (array $backends) => ['dependencies' => ['backend' => $backends]],
800
+            $appBackends,
801
+        );
802
+
803
+        $reflection = new \ReflectionClass($this->manager);
804
+        $property = $reflection->getProperty('appInfos');
805
+        $property->setValue($this->manager, $appInfoData);
806
+
807
+        $this->assertEquals($expected, $this->manager->isBackendRequired($backend));
808
+    }
809
+
810
+    public function testGetAppVersion() {
811
+        $manager = $this->getMockBuilder(AppManager::class)
812
+            ->setConstructorArgs([
813
+                $this->userSession,
814
+                $this->config,
815
+                $this->groupManager,
816
+                $this->cacheFactory,
817
+                $this->eventDispatcher,
818
+                $this->logger,
819
+                $this->serverVersion,
820
+            ])
821
+            ->onlyMethods([
822
+                'getAppInfo',
823
+            ])
824
+            ->getMock();
825
+
826
+        $manager->expects(self::once())
827
+            ->method('getAppInfo')
828
+            ->with('myapp')
829
+            ->willReturn(['version' => '99.99.99-rc.99']);
830
+
831
+        $this->serverVersion
832
+            ->expects(self::never())
833
+            ->method('getVersionString');
834
+
835
+        $this->assertEquals(
836
+            '99.99.99-rc.99',
837
+            $manager->getAppVersion('myapp'),
838
+        );
839
+    }
840
+
841
+    public function testGetAppVersionCore() {
842
+        $manager = $this->getMockBuilder(AppManager::class)
843
+            ->setConstructorArgs([
844
+                $this->userSession,
845
+                $this->config,
846
+                $this->groupManager,
847
+                $this->cacheFactory,
848
+                $this->eventDispatcher,
849
+                $this->logger,
850
+                $this->serverVersion,
851
+            ])
852
+            ->onlyMethods([
853
+                'getAppInfo',
854
+            ])
855
+            ->getMock();
856
+
857
+        $manager->expects(self::never())
858
+            ->method('getAppInfo');
859
+
860
+        $this->serverVersion
861
+            ->expects(self::once())
862
+            ->method('getVersionString')
863
+            ->willReturn('1.2.3-beta.4');
864
+
865
+        $this->assertEquals(
866
+            '1.2.3-beta.4',
867
+            $manager->getAppVersion('core'),
868
+        );
869
+    }
870
+
871
+    public function testGetAppVersionUnknown() {
872
+        $manager = $this->getMockBuilder(AppManager::class)
873
+            ->setConstructorArgs([
874
+                $this->userSession,
875
+                $this->config,
876
+                $this->groupManager,
877
+                $this->cacheFactory,
878
+                $this->eventDispatcher,
879
+                $this->logger,
880
+                $this->serverVersion,
881
+            ])
882
+            ->onlyMethods([
883
+                'getAppInfo',
884
+            ])
885
+            ->getMock();
886
+
887
+        $manager->expects(self::once())
888
+            ->method('getAppInfo')
889
+            ->with('unknown')
890
+            ->willReturn(null);
891
+
892
+        $this->serverVersion
893
+            ->expects(self::never())
894
+            ->method('getVersionString');
895
+
896
+        $this->assertEquals(
897
+            '0',
898
+            $manager->getAppVersion('unknown'),
899
+        );
900
+    }
901 901
 
902 902
 }
Please login to merge, or discard this patch.
Spacing   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -45,12 +45,12 @@  discard block
 block discarded – undo
45 45
 
46 46
 		$config->expects($this->any())
47 47
 			->method('getValue')
48
-			->willReturnCallback(function ($app, $key, $default) use (&$appConfig) {
48
+			->willReturnCallback(function($app, $key, $default) use (&$appConfig) {
49 49
 				return (isset($appConfig[$app]) and isset($appConfig[$app][$key])) ? $appConfig[$app][$key] : $default;
50 50
 			});
51 51
 		$config->expects($this->any())
52 52
 			->method('setValue')
53
-			->willReturnCallback(function ($app, $key, $value) use (&$appConfig) {
53
+			->willReturnCallback(function($app, $key, $value) use (&$appConfig) {
54 54
 				if (!isset($appConfig[$app])) {
55 55
 					$appConfig[$app] = [];
56 56
 				}
@@ -58,7 +58,7 @@  discard block
 block discarded – undo
58 58
 			});
59 59
 		$config->expects($this->any())
60 60
 			->method('getValues')
61
-			->willReturnCallback(function ($app, $key) use (&$appConfig) {
61
+			->willReturnCallback(function($app, $key) use (&$appConfig) {
62 62
 				if ($app) {
63 63
 					return $appConfig[$app];
64 64
 				} else {
@@ -73,7 +73,7 @@  discard block
 block discarded – undo
73 73
 			});
74 74
 		$config->expects($this->any())
75 75
 			->method('searchValues')
76
-			->willReturnCallback(function ($key, $lazy, $type) use (&$appConfig) {
76
+			->willReturnCallback(function($key, $lazy, $type) use (&$appConfig) {
77 77
 				$values = [];
78 78
 				foreach ($appConfig as $appid => $appData) {
79 79
 					if (isset($appData[$key])) {
@@ -171,16 +171,16 @@  discard block
 block discarded – undo
171 171
 	}
172 172
 
173 173
 	public static function dataGetAppIcon(): array {
174
-		$nothing = function ($appId) {
174
+		$nothing = function($appId) {
175 175
 			self::assertEquals('test', $appId);
176 176
 			throw new \RuntimeException();
177 177
 		};
178 178
 
179
-		$createCallback = function ($workingIcons) {
180
-			return function ($appId, $icon) use ($workingIcons) {
179
+		$createCallback = function($workingIcons) {
180
+			return function($appId, $icon) use ($workingIcons) {
181 181
 				self::assertEquals('test', $appId);
182 182
 				if (in_array($icon, $workingIcons)) {
183
-					return '/path/' . $icon;
183
+					return '/path/'.$icon;
184 184
 				}
185 185
 				throw new \RuntimeException();
186 186
 			};
@@ -471,13 +471,13 @@  discard block
 block discarded – undo
471 471
 	}
472 472
 
473 473
 	public function testGetAppPath(): void {
474
-		$this->assertEquals(\OC::$SERVERROOT . '/apps/files', $this->manager->getAppPath('files'));
474
+		$this->assertEquals(\OC::$SERVERROOT.'/apps/files', $this->manager->getAppPath('files'));
475 475
 	}
476 476
 
477 477
 	public function testGetAppPathSymlink(): void {
478 478
 		$fakeAppDirname = sha1(uniqid('test', true));
479
-		$fakeAppPath = sys_get_temp_dir() . '/' . $fakeAppDirname;
480
-		$fakeAppLink = \OC::$SERVERROOT . '/' . $fakeAppDirname;
479
+		$fakeAppPath = sys_get_temp_dir().'/'.$fakeAppDirname;
480
+		$fakeAppLink = \OC::$SERVERROOT.'/'.$fakeAppDirname;
481 481
 
482 482
 		mkdir($fakeAppPath);
483 483
 		if (symlink($fakeAppPath, $fakeAppLink) === false) {
@@ -487,11 +487,11 @@  discard block
 block discarded – undo
487 487
 		// Use the symlink as the app path
488 488
 		\OC::$APPSROOTS[] = [
489 489
 			'path' => $fakeAppLink,
490
-			'url' => \OC::$WEBROOT . '/' . $fakeAppDirname,
490
+			'url' => \OC::$WEBROOT.'/'.$fakeAppDirname,
491 491
 			'writable' => false,
492 492
 		];
493 493
 
494
-		$fakeTestAppPath = $fakeAppPath . '/' . 'test-test-app';
494
+		$fakeTestAppPath = $fakeAppPath.'/'.'test-test-app';
495 495
 		mkdir($fakeTestAppPath);
496 496
 
497 497
 		$generatedAppPath = $this->manager->getAppPath('test-test-app');
@@ -500,7 +500,7 @@  discard block
 block discarded – undo
500 500
 		unlink($fakeAppLink);
501 501
 		rmdir($fakeAppPath);
502 502
 
503
-		$this->assertEquals($fakeAppLink . '/test-test-app', $generatedAppPath);
503
+		$this->assertEquals($fakeAppLink.'/test-test-app', $generatedAppPath);
504 504
 	}
505 505
 
506 506
 	public function testGetAppPathFail(): void {
@@ -644,7 +644,7 @@  discard block
 block discarded – undo
644 644
 		$manager->expects($this->any())
645 645
 			->method('getAppInfo')
646 646
 			->willReturnCallback(
647
-				function ($appId) use ($appInfos) {
647
+				function($appId) use ($appInfos) {
648 648
 					return $appInfos[$appId];
649 649
 				}
650 650
 			);
@@ -703,7 +703,7 @@  discard block
 block discarded – undo
703 703
 		$manager->expects($this->any())
704 704
 			->method('getAppInfo')
705 705
 			->willReturnCallback(
706
-				function ($appId) use ($appInfos) {
706
+				function($appId) use ($appInfos) {
707 707
 					return $appInfos[$appId];
708 708
 				}
709 709
 			);
Please login to merge, or discard this patch.
lib/private/Server.php 2 patches
Indentation   +1433 added lines, -1433 removed lines patch added patch discarded remove patch
@@ -248,1474 +248,1474 @@
 block discarded – undo
248 248
  * TODO: hookup all manager classes
249 249
  */
250 250
 class Server extends ServerContainer implements IServerContainer {
251
-	/** @var string */
252
-	private $webRoot;
253
-
254
-	/**
255
-	 * @param string $webRoot
256
-	 * @param \OC\Config $config
257
-	 */
258
-	public function __construct($webRoot, \OC\Config $config) {
259
-		parent::__construct();
260
-		$this->webRoot = $webRoot;
261
-
262
-		// To find out if we are running from CLI or not
263
-		$this->registerParameter('isCLI', \OC::$CLI);
264
-		$this->registerParameter('serverRoot', \OC::$SERVERROOT);
265
-
266
-		$this->registerService(ContainerInterface::class, function (ContainerInterface $c) {
267
-			return $c;
268
-		});
269
-		$this->registerService(\OCP\IServerContainer::class, function (ContainerInterface $c) {
270
-			return $c;
271
-		});
272
-
273
-		$this->registerAlias(\OCP\Calendar\IManager::class, \OC\Calendar\Manager::class);
274
-
275
-		$this->registerAlias(\OCP\Calendar\Resource\IManager::class, \OC\Calendar\Resource\Manager::class);
276
-
277
-		$this->registerAlias(\OCP\Calendar\Room\IManager::class, \OC\Calendar\Room\Manager::class);
278
-
279
-		$this->registerAlias(\OCP\Contacts\IManager::class, \OC\ContactsManager::class);
280
-
281
-		$this->registerAlias(\OCP\DirectEditing\IManager::class, \OC\DirectEditing\Manager::class);
282
-		$this->registerAlias(ITemplateManager::class, TemplateManager::class);
283
-		$this->registerAlias(\OCP\Template\ITemplateManager::class, \OC\Template\TemplateManager::class);
284
-
285
-		$this->registerAlias(IActionFactory::class, ActionFactory::class);
286
-
287
-		$this->registerService(View::class, function (Server $c) {
288
-			return new View();
289
-		}, false);
290
-
291
-		$this->registerService(IPreview::class, function (ContainerInterface $c) {
292
-			return new PreviewManager(
293
-				$c->get(\OCP\IConfig::class),
294
-				$c->get(IRootFolder::class),
295
-				new \OC\Preview\Storage\Root(
296
-					$c->get(IRootFolder::class),
297
-					$c->get(SystemConfig::class)
298
-				),
299
-				$c->get(IEventDispatcher::class),
300
-				$c->get(GeneratorHelper::class),
301
-				$c->get(ISession::class)->get('user_id'),
302
-				$c->get(Coordinator::class),
303
-				$c->get(IServerContainer::class),
304
-				$c->get(IBinaryFinder::class),
305
-				$c->get(IMagickSupport::class)
306
-			);
307
-		});
308
-		$this->registerAlias(IMimeIconProvider::class, MimeIconProvider::class);
309
-
310
-		$this->registerService(\OC\Preview\Watcher::class, function (ContainerInterface $c) {
311
-			return new \OC\Preview\Watcher(
312
-				new \OC\Preview\Storage\Root(
313
-					$c->get(IRootFolder::class),
314
-					$c->get(SystemConfig::class)
315
-				)
316
-			);
317
-		});
318
-
319
-		$this->registerService(IProfiler::class, function (Server $c) {
320
-			return new Profiler($c->get(SystemConfig::class));
321
-		});
322
-
323
-		$this->registerService(\OCP\Encryption\IManager::class, function (Server $c): Encryption\Manager {
324
-			$view = new View();
325
-			$util = new Encryption\Util(
326
-				$view,
327
-				$c->get(IUserManager::class),
328
-				$c->get(IGroupManager::class),
329
-				$c->get(\OCP\IConfig::class)
330
-			);
331
-			return new Encryption\Manager(
332
-				$c->get(\OCP\IConfig::class),
333
-				$c->get(LoggerInterface::class),
334
-				$c->getL10N('core'),
335
-				new View(),
336
-				$util,
337
-				new ArrayCache()
338
-			);
339
-		});
340
-
341
-		$this->registerService(IFile::class, function (ContainerInterface $c) {
342
-			$util = new Encryption\Util(
343
-				new View(),
344
-				$c->get(IUserManager::class),
345
-				$c->get(IGroupManager::class),
346
-				$c->get(\OCP\IConfig::class)
347
-			);
348
-			return new Encryption\File(
349
-				$util,
350
-				$c->get(IRootFolder::class),
351
-				$c->get(\OCP\Share\IManager::class)
352
-			);
353
-		});
354
-
355
-		$this->registerService(IStorage::class, function (ContainerInterface $c) {
356
-			$view = new View();
357
-			$util = new Encryption\Util(
358
-				$view,
359
-				$c->get(IUserManager::class),
360
-				$c->get(IGroupManager::class),
361
-				$c->get(\OCP\IConfig::class)
362
-			);
363
-
364
-			return new Encryption\Keys\Storage(
365
-				$view,
366
-				$util,
367
-				$c->get(ICrypto::class),
368
-				$c->get(\OCP\IConfig::class)
369
-			);
370
-		});
371
-
372
-		$this->registerAlias(\OCP\ITagManager::class, TagManager::class);
373
-
374
-		$this->registerService('SystemTagManagerFactory', function (ContainerInterface $c) {
375
-			/** @var \OCP\IConfig $config */
376
-			$config = $c->get(\OCP\IConfig::class);
377
-			$factoryClass = $config->getSystemValue('systemtags.managerFactory', SystemTagManagerFactory::class);
378
-			return new $factoryClass($this);
379
-		});
380
-		$this->registerService(ISystemTagManager::class, function (ContainerInterface $c) {
381
-			return $c->get('SystemTagManagerFactory')->getManager();
382
-		});
383
-		/** @deprecated 19.0.0 */
384
-		$this->registerDeprecatedAlias('SystemTagManager', ISystemTagManager::class);
385
-
386
-		$this->registerService(ISystemTagObjectMapper::class, function (ContainerInterface $c) {
387
-			return $c->get('SystemTagManagerFactory')->getObjectMapper();
388
-		});
389
-		$this->registerAlias(IFileAccess::class, FileAccess::class);
390
-		$this->registerService('RootFolder', function (ContainerInterface $c) {
391
-			$manager = \OC\Files\Filesystem::getMountManager();
392
-			$view = new View();
393
-			/** @var IUserSession $userSession */
394
-			$userSession = $c->get(IUserSession::class);
395
-			$root = new Root(
396
-				$manager,
397
-				$view,
398
-				$userSession->getUser(),
399
-				$c->get(IUserMountCache::class),
400
-				$this->get(LoggerInterface::class),
401
-				$this->get(IUserManager::class),
402
-				$this->get(IEventDispatcher::class),
403
-				$this->get(ICacheFactory::class),
404
-			);
405
-
406
-			$previewConnector = new \OC\Preview\WatcherConnector(
407
-				$root,
408
-				$c->get(SystemConfig::class),
409
-				$this->get(IEventDispatcher::class)
410
-			);
411
-			$previewConnector->connectWatcher();
412
-
413
-			return $root;
414
-		});
415
-		$this->registerService(HookConnector::class, function (ContainerInterface $c) {
416
-			return new HookConnector(
417
-				$c->get(IRootFolder::class),
418
-				new View(),
419
-				$c->get(IEventDispatcher::class),
420
-				$c->get(LoggerInterface::class)
421
-			);
422
-		});
423
-
424
-		$this->registerService(IRootFolder::class, function (ContainerInterface $c) {
425
-			return new LazyRoot(function () use ($c) {
426
-				return $c->get('RootFolder');
427
-			});
428
-		});
429
-
430
-		$this->registerAlias(\OCP\IUserManager::class, \OC\User\Manager::class);
431
-
432
-		$this->registerService(DisplayNameCache::class, function (ContainerInterface $c) {
433
-			return $c->get(\OC\User\Manager::class)->getDisplayNameCache();
434
-		});
435
-
436
-		$this->registerService(\OCP\IGroupManager::class, function (ContainerInterface $c) {
437
-			$groupManager = new \OC\Group\Manager(
438
-				$this->get(IUserManager::class),
439
-				$this->get(IEventDispatcher::class),
440
-				$this->get(LoggerInterface::class),
441
-				$this->get(ICacheFactory::class),
442
-				$this->get(IRemoteAddress::class),
443
-			);
444
-			return $groupManager;
445
-		});
446
-
447
-		$this->registerService(Store::class, function (ContainerInterface $c) {
448
-			$session = $c->get(ISession::class);
449
-			if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
450
-				$tokenProvider = $c->get(IProvider::class);
451
-			} else {
452
-				$tokenProvider = null;
453
-			}
454
-			$logger = $c->get(LoggerInterface::class);
455
-			$crypto = $c->get(ICrypto::class);
456
-			return new Store($session, $logger, $crypto, $tokenProvider);
457
-		});
458
-		$this->registerAlias(IStore::class, Store::class);
459
-		$this->registerAlias(IProvider::class, Authentication\Token\Manager::class);
460
-		$this->registerAlias(OCPIProvider::class, Authentication\Token\Manager::class);
461
-
462
-		$this->registerService(\OC\User\Session::class, function (Server $c) {
463
-			$manager = $c->get(IUserManager::class);
464
-			$session = new \OC\Session\Memory();
465
-			$timeFactory = new TimeFactory();
466
-			// Token providers might require a working database. This code
467
-			// might however be called when Nextcloud is not yet setup.
468
-			if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
469
-				$provider = $c->get(IProvider::class);
470
-			} else {
471
-				$provider = null;
472
-			}
473
-
474
-			$userSession = new \OC\User\Session(
475
-				$manager,
476
-				$session,
477
-				$timeFactory,
478
-				$provider,
479
-				$c->get(\OCP\IConfig::class),
480
-				$c->get(ISecureRandom::class),
481
-				$c->get('LockdownManager'),
482
-				$c->get(LoggerInterface::class),
483
-				$c->get(IEventDispatcher::class),
484
-			);
485
-			/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
486
-			$userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) {
487
-				\OC_Hook::emit('OC_User', 'pre_createUser', ['run' => true, 'uid' => $uid, 'password' => $password]);
488
-			});
489
-			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
490
-			$userSession->listen('\OC\User', 'postCreateUser', function ($user, $password) {
491
-				/** @var \OC\User\User $user */
492
-				\OC_Hook::emit('OC_User', 'post_createUser', ['uid' => $user->getUID(), 'password' => $password]);
493
-			});
494
-			/** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
495
-			$userSession->listen('\OC\User', 'preDelete', function ($user) {
496
-				/** @var \OC\User\User $user */
497
-				\OC_Hook::emit('OC_User', 'pre_deleteUser', ['run' => true, 'uid' => $user->getUID()]);
498
-			});
499
-			/** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
500
-			$userSession->listen('\OC\User', 'postDelete', function ($user) {
501
-				/** @var \OC\User\User $user */
502
-				\OC_Hook::emit('OC_User', 'post_deleteUser', ['uid' => $user->getUID()]);
503
-			});
504
-			$userSession->listen('\OC\User', 'preSetPassword', function ($user, $password, $recoveryPassword) {
505
-				/** @var \OC\User\User $user */
506
-				\OC_Hook::emit('OC_User', 'pre_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
507
-			});
508
-			$userSession->listen('\OC\User', 'postSetPassword', function ($user, $password, $recoveryPassword) {
509
-				/** @var \OC\User\User $user */
510
-				\OC_Hook::emit('OC_User', 'post_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
511
-			});
512
-			$userSession->listen('\OC\User', 'preLogin', function ($uid, $password) {
513
-				\OC_Hook::emit('OC_User', 'pre_login', ['run' => true, 'uid' => $uid, 'password' => $password]);
514
-
515
-				/** @var IEventDispatcher $dispatcher */
516
-				$dispatcher = $this->get(IEventDispatcher::class);
517
-				$dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password));
518
-			});
519
-			$userSession->listen('\OC\User', 'postLogin', function ($user, $loginName, $password, $isTokenLogin) {
520
-				/** @var \OC\User\User $user */
521
-				\OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'loginName' => $loginName, 'password' => $password, 'isTokenLogin' => $isTokenLogin]);
522
-
523
-				/** @var IEventDispatcher $dispatcher */
524
-				$dispatcher = $this->get(IEventDispatcher::class);
525
-				$dispatcher->dispatchTyped(new UserLoggedInEvent($user, $loginName, $password, $isTokenLogin));
526
-			});
527
-			$userSession->listen('\OC\User', 'preRememberedLogin', function ($uid) {
528
-				/** @var IEventDispatcher $dispatcher */
529
-				$dispatcher = $this->get(IEventDispatcher::class);
530
-				$dispatcher->dispatchTyped(new BeforeUserLoggedInWithCookieEvent($uid));
531
-			});
532
-			$userSession->listen('\OC\User', 'postRememberedLogin', function ($user, $password) {
533
-				/** @var \OC\User\User $user */
534
-				\OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'password' => $password]);
535
-
536
-				/** @var IEventDispatcher $dispatcher */
537
-				$dispatcher = $this->get(IEventDispatcher::class);
538
-				$dispatcher->dispatchTyped(new UserLoggedInWithCookieEvent($user, $password));
539
-			});
540
-			$userSession->listen('\OC\User', 'logout', function ($user) {
541
-				\OC_Hook::emit('OC_User', 'logout', []);
542
-
543
-				/** @var IEventDispatcher $dispatcher */
544
-				$dispatcher = $this->get(IEventDispatcher::class);
545
-				$dispatcher->dispatchTyped(new BeforeUserLoggedOutEvent($user));
546
-			});
547
-			$userSession->listen('\OC\User', 'postLogout', function ($user) {
548
-				/** @var IEventDispatcher $dispatcher */
549
-				$dispatcher = $this->get(IEventDispatcher::class);
550
-				$dispatcher->dispatchTyped(new UserLoggedOutEvent($user));
551
-			});
552
-			$userSession->listen('\OC\User', 'changeUser', function ($user, $feature, $value, $oldValue) {
553
-				/** @var \OC\User\User $user */
554
-				\OC_Hook::emit('OC_User', 'changeUser', ['run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value, 'old_value' => $oldValue]);
555
-			});
556
-			return $userSession;
557
-		});
558
-		$this->registerAlias(\OCP\IUserSession::class, \OC\User\Session::class);
559
-
560
-		$this->registerAlias(\OCP\Authentication\TwoFactorAuth\IRegistry::class, \OC\Authentication\TwoFactorAuth\Registry::class);
561
-
562
-		$this->registerAlias(INavigationManager::class, \OC\NavigationManager::class);
563
-
564
-		$this->registerAlias(\OCP\IConfig::class, \OC\AllConfig::class);
565
-
566
-		$this->registerService(\OC\SystemConfig::class, function ($c) use ($config) {
567
-			return new \OC\SystemConfig($config);
568
-		});
569
-
570
-		$this->registerAlias(IAppConfig::class, \OC\AppConfig::class);
571
-		$this->registerAlias(IUserConfig::class, \OC\Config\UserConfig::class);
572
-
573
-		$this->registerService(IFactory::class, function (Server $c) {
574
-			return new \OC\L10N\Factory(
575
-				$c->get(\OCP\IConfig::class),
576
-				$c->getRequest(),
577
-				$c->get(IUserSession::class),
578
-				$c->get(ICacheFactory::class),
579
-				\OC::$SERVERROOT,
580
-				$c->get(IAppManager::class),
581
-			);
582
-		});
583
-
584
-		$this->registerAlias(IURLGenerator::class, URLGenerator::class);
585
-
586
-		$this->registerService(ICache::class, function ($c) {
587
-			return new Cache\File();
588
-		});
589
-
590
-		$this->registerService(Factory::class, function (Server $c) {
591
-			$profiler = $c->get(IProfiler::class);
592
-			$arrayCacheFactory = new \OC\Memcache\Factory(fn () => '', $c->get(LoggerInterface::class),
593
-				$profiler,
594
-				ArrayCache::class,
595
-				ArrayCache::class,
596
-				ArrayCache::class
597
-			);
598
-			/** @var SystemConfig $config */
599
-			$config = $c->get(SystemConfig::class);
600
-			/** @var ServerVersion $serverVersion */
601
-			$serverVersion = $c->get(ServerVersion::class);
602
-
603
-			if ($config->getValue('installed', false) && !(defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
604
-				$logQuery = $config->getValue('log_query');
605
-				$prefixClosure = function () use ($logQuery, $serverVersion): ?string {
606
-					if (!$logQuery) {
607
-						try {
608
-							$v = \OCP\Server::get(IAppConfig::class)->getAppInstalledVersions(true);
609
-						} catch (\Doctrine\DBAL\Exception $e) {
610
-							// Database service probably unavailable
611
-							// Probably related to https://github.com/nextcloud/server/issues/37424
612
-							return null;
613
-						}
614
-					} else {
615
-						// If the log_query is enabled, we can not get the app versions
616
-						// as that does a query, which will be logged and the logging
617
-						// depends on redis and here we are back again in the same function.
618
-						$v = [
619
-							'log_query' => 'enabled',
620
-						];
621
-					}
622
-					$v['core'] = implode(',', $serverVersion->getVersion());
623
-					$version = implode(',', array_keys($v)) . implode(',', $v);
624
-					$instanceId = \OC_Util::getInstanceId();
625
-					$path = \OC::$SERVERROOT;
626
-					return md5($instanceId . '-' . $version . '-' . $path);
627
-				};
628
-				return new \OC\Memcache\Factory($prefixClosure,
629
-					$c->get(LoggerInterface::class),
630
-					$profiler,
631
-					/** @psalm-taint-escape callable */
632
-					$config->getValue('memcache.local', null),
633
-					/** @psalm-taint-escape callable */
634
-					$config->getValue('memcache.distributed', null),
635
-					/** @psalm-taint-escape callable */
636
-					$config->getValue('memcache.locking', null),
637
-					/** @psalm-taint-escape callable */
638
-					$config->getValue('redis_log_file')
639
-				);
640
-			}
641
-			return $arrayCacheFactory;
642
-		});
643
-		$this->registerAlias(ICacheFactory::class, Factory::class);
644
-
645
-		$this->registerService('RedisFactory', function (Server $c) {
646
-			$systemConfig = $c->get(SystemConfig::class);
647
-			return new RedisFactory($systemConfig, $c->get(IEventLogger::class));
648
-		});
649
-
650
-		$this->registerService(\OCP\Activity\IManager::class, function (Server $c) {
651
-			$l10n = $this->get(IFactory::class)->get('lib');
652
-			return new \OC\Activity\Manager(
653
-				$c->getRequest(),
654
-				$c->get(IUserSession::class),
655
-				$c->get(\OCP\IConfig::class),
656
-				$c->get(IValidator::class),
657
-				$c->get(IRichTextFormatter::class),
658
-				$l10n
659
-			);
660
-		});
661
-
662
-		$this->registerService(\OCP\Activity\IEventMerger::class, function (Server $c) {
663
-			return new \OC\Activity\EventMerger(
664
-				$c->getL10N('lib')
665
-			);
666
-		});
667
-		$this->registerAlias(IValidator::class, Validator::class);
668
-
669
-		$this->registerService(AvatarManager::class, function (Server $c) {
670
-			return new AvatarManager(
671
-				$c->get(IUserSession::class),
672
-				$c->get(\OC\User\Manager::class),
673
-				$c->getAppDataDir('avatar'),
674
-				$c->getL10N('lib'),
675
-				$c->get(LoggerInterface::class),
676
-				$c->get(\OCP\IConfig::class),
677
-				$c->get(IAccountManager::class),
678
-				$c->get(KnownUserService::class)
679
-			);
680
-		});
681
-
682
-		$this->registerAlias(IAvatarManager::class, AvatarManager::class);
683
-
684
-		$this->registerAlias(\OCP\Support\CrashReport\IRegistry::class, \OC\Support\CrashReport\Registry::class);
685
-		$this->registerAlias(\OCP\Support\Subscription\IRegistry::class, \OC\Support\Subscription\Registry::class);
686
-		$this->registerAlias(\OCP\Support\Subscription\IAssertion::class, \OC\Support\Subscription\Assertion::class);
687
-
688
-		/** Only used by the PsrLoggerAdapter should not be used by apps */
689
-		$this->registerService(\OC\Log::class, function (Server $c) {
690
-			$logType = $c->get(AllConfig::class)->getSystemValue('log_type', 'file');
691
-			$factory = new LogFactory($c, $this->get(SystemConfig::class));
692
-			$logger = $factory->get($logType);
693
-			$registry = $c->get(\OCP\Support\CrashReport\IRegistry::class);
694
-
695
-			return new Log($logger, $this->get(SystemConfig::class), crashReporters: $registry);
696
-		});
697
-		// PSR-3 logger
698
-		$this->registerAlias(LoggerInterface::class, PsrLoggerAdapter::class);
699
-
700
-		$this->registerService(ILogFactory::class, function (Server $c) {
701
-			return new LogFactory($c, $this->get(SystemConfig::class));
702
-		});
703
-
704
-		$this->registerAlias(IJobList::class, \OC\BackgroundJob\JobList::class);
705
-
706
-		$this->registerService(Router::class, function (Server $c) {
707
-			$cacheFactory = $c->get(ICacheFactory::class);
708
-			if ($cacheFactory->isLocalCacheAvailable()) {
709
-				$router = $c->resolve(CachingRouter::class);
710
-			} else {
711
-				$router = $c->resolve(Router::class);
712
-			}
713
-			return $router;
714
-		});
715
-		$this->registerAlias(IRouter::class, Router::class);
716
-
717
-		$this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function ($c) {
718
-			$config = $c->get(\OCP\IConfig::class);
719
-			if (ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
720
-				$backend = new \OC\Security\RateLimiting\Backend\MemoryCacheBackend(
721
-					$c->get(AllConfig::class),
722
-					$this->get(ICacheFactory::class),
723
-					new \OC\AppFramework\Utility\TimeFactory()
724
-				);
725
-			} else {
726
-				$backend = new \OC\Security\RateLimiting\Backend\DatabaseBackend(
727
-					$c->get(AllConfig::class),
728
-					$c->get(IDBConnection::class),
729
-					new \OC\AppFramework\Utility\TimeFactory()
730
-				);
731
-			}
732
-
733
-			return $backend;
734
-		});
735
-
736
-		$this->registerAlias(\OCP\Security\ISecureRandom::class, SecureRandom::class);
737
-		$this->registerAlias(\OCP\Security\IRemoteHostValidator::class, \OC\Security\RemoteHostValidator::class);
738
-		$this->registerAlias(IVerificationToken::class, VerificationToken::class);
739
-
740
-		$this->registerAlias(ICrypto::class, Crypto::class);
741
-
742
-		$this->registerAlias(IHasher::class, Hasher::class);
743
-
744
-		$this->registerAlias(ICredentialsManager::class, CredentialsManager::class);
745
-
746
-		$this->registerAlias(IDBConnection::class, ConnectionAdapter::class);
747
-		$this->registerService(Connection::class, function (Server $c) {
748
-			$systemConfig = $c->get(SystemConfig::class);
749
-			$factory = new \OC\DB\ConnectionFactory($systemConfig, $c->get(ICacheFactory::class));
750
-			$type = $systemConfig->getValue('dbtype', 'sqlite');
751
-			if (!$factory->isValidType($type)) {
752
-				throw new \OC\DatabaseException('Invalid database type');
753
-			}
754
-			$connection = $factory->getConnection($type, []);
755
-			return $connection;
756
-		});
757
-
758
-		$this->registerAlias(ICertificateManager::class, CertificateManager::class);
759
-		$this->registerAlias(IClientService::class, ClientService::class);
760
-		$this->registerService(NegativeDnsCache::class, function (ContainerInterface $c) {
761
-			return new NegativeDnsCache(
762
-				$c->get(ICacheFactory::class),
763
-			);
764
-		});
765
-		$this->registerDeprecatedAlias('HttpClientService', IClientService::class);
766
-		$this->registerService(IEventLogger::class, function (ContainerInterface $c) {
767
-			return new EventLogger($c->get(SystemConfig::class), $c->get(LoggerInterface::class), $c->get(Log::class));
768
-		});
769
-
770
-		$this->registerService(IQueryLogger::class, function (ContainerInterface $c) {
771
-			$queryLogger = new QueryLogger();
772
-			if ($c->get(SystemConfig::class)->getValue('debug', false)) {
773
-				// In debug mode, module is being activated by default
774
-				$queryLogger->activate();
775
-			}
776
-			return $queryLogger;
777
-		});
778
-
779
-		$this->registerAlias(ITempManager::class, TempManager::class);
780
-
781
-		$this->registerService(AppManager::class, function (ContainerInterface $c) {
782
-			// TODO: use auto-wiring
783
-			return new \OC\App\AppManager(
784
-				$c->get(IUserSession::class),
785
-				$c->get(\OCP\IConfig::class),
786
-				$c->get(IGroupManager::class),
787
-				$c->get(ICacheFactory::class),
788
-				$c->get(IEventDispatcher::class),
789
-				$c->get(LoggerInterface::class),
790
-				$c->get(ServerVersion::class),
791
-			);
792
-		});
793
-		$this->registerAlias(IAppManager::class, AppManager::class);
794
-
795
-		$this->registerAlias(IDateTimeZone::class, DateTimeZone::class);
796
-
797
-		$this->registerService(IDateTimeFormatter::class, function (Server $c) {
798
-			$language = $c->get(\OCP\IConfig::class)->getUserValue($c->get(ISession::class)->get('user_id'), 'core', 'lang', null);
799
-
800
-			return new DateTimeFormatter(
801
-				$c->get(IDateTimeZone::class)->getTimeZone(),
802
-				$c->getL10N('lib', $language)
803
-			);
804
-		});
805
-
806
-		$this->registerService(IUserMountCache::class, function (ContainerInterface $c) {
807
-			$mountCache = $c->get(UserMountCache::class);
808
-			$listener = new UserMountCacheListener($mountCache);
809
-			$listener->listen($c->get(IUserManager::class));
810
-			return $mountCache;
811
-		});
812
-
813
-		$this->registerService(IMountProviderCollection::class, function (ContainerInterface $c) {
814
-			$loader = $c->get(IStorageFactory::class);
815
-			$mountCache = $c->get(IUserMountCache::class);
816
-			$eventLogger = $c->get(IEventLogger::class);
817
-			$manager = new MountProviderCollection($loader, $mountCache, $eventLogger);
818
-
819
-			// builtin providers
820
-
821
-			$config = $c->get(\OCP\IConfig::class);
822
-			$logger = $c->get(LoggerInterface::class);
823
-			$objectStoreConfig = $c->get(PrimaryObjectStoreConfig::class);
824
-			$manager->registerProvider(new CacheMountProvider($config));
825
-			$manager->registerHomeProvider(new LocalHomeMountProvider());
826
-			$manager->registerHomeProvider(new ObjectHomeMountProvider($objectStoreConfig));
827
-			$manager->registerRootProvider(new RootMountProvider($objectStoreConfig, $config));
828
-			$manager->registerRootProvider(new ObjectStorePreviewCacheMountProvider($logger, $config));
829
-
830
-			return $manager;
831
-		});
832
-
833
-		$this->registerService(IBus::class, function (ContainerInterface $c) {
834
-			$busClass = $c->get(\OCP\IConfig::class)->getSystemValueString('commandbus');
835
-			if ($busClass) {
836
-				[$app, $class] = explode('::', $busClass, 2);
837
-				if ($c->get(IAppManager::class)->isEnabledForUser($app)) {
838
-					$c->get(IAppManager::class)->loadApp($app);
839
-					return $c->get($class);
840
-				} else {
841
-					throw new ServiceUnavailableException("The app providing the command bus ($app) is not enabled");
842
-				}
843
-			} else {
844
-				$jobList = $c->get(IJobList::class);
845
-				return new CronBus($jobList);
846
-			}
847
-		});
848
-		$this->registerDeprecatedAlias('AsyncCommandBus', IBus::class);
849
-		$this->registerAlias(ITrustedDomainHelper::class, TrustedDomainHelper::class);
850
-		$this->registerAlias(IThrottler::class, Throttler::class);
851
-
852
-		$this->registerService(\OC\Security\Bruteforce\Backend\IBackend::class, function ($c) {
853
-			$config = $c->get(\OCP\IConfig::class);
854
-			if (!$config->getSystemValueBool('auth.bruteforce.protection.force.database', false)
855
-				&& ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
856
-				$backend = $c->get(\OC\Security\Bruteforce\Backend\MemoryCacheBackend::class);
857
-			} else {
858
-				$backend = $c->get(\OC\Security\Bruteforce\Backend\DatabaseBackend::class);
859
-			}
860
-
861
-			return $backend;
862
-		});
863
-
864
-		$this->registerDeprecatedAlias('IntegrityCodeChecker', Checker::class);
865
-		$this->registerService(Checker::class, function (ContainerInterface $c) {
866
-			// IConfig requires a working database. This code
867
-			// might however be called when Nextcloud is not yet setup.
868
-			if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
869
-				$config = $c->get(\OCP\IConfig::class);
870
-				$appConfig = $c->get(\OCP\IAppConfig::class);
871
-			} else {
872
-				$config = null;
873
-				$appConfig = null;
874
-			}
875
-
876
-			return new Checker(
877
-				$c->get(ServerVersion::class),
878
-				$c->get(EnvironmentHelper::class),
879
-				new FileAccessHelper(),
880
-				new AppLocator(),
881
-				$config,
882
-				$appConfig,
883
-				$c->get(ICacheFactory::class),
884
-				$c->get(IAppManager::class),
885
-				$c->get(IMimeTypeDetector::class)
886
-			);
887
-		});
888
-		$this->registerService(\OCP\IRequest::class, function (ContainerInterface $c) {
889
-			if (isset($this['urlParams'])) {
890
-				$urlParams = $this['urlParams'];
891
-			} else {
892
-				$urlParams = [];
893
-			}
894
-
895
-			if (defined('PHPUNIT_RUN') && PHPUNIT_RUN
896
-				&& in_array('fakeinput', stream_get_wrappers())
897
-			) {
898
-				$stream = 'fakeinput://data';
899
-			} else {
900
-				$stream = 'php://input';
901
-			}
902
-
903
-			return new Request(
904
-				[
905
-					'get' => $_GET,
906
-					'post' => $_POST,
907
-					'files' => $_FILES,
908
-					'server' => $_SERVER,
909
-					'env' => $_ENV,
910
-					'cookies' => $_COOKIE,
911
-					'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
912
-						? $_SERVER['REQUEST_METHOD']
913
-						: '',
914
-					'urlParams' => $urlParams,
915
-				],
916
-				$this->get(IRequestId::class),
917
-				$this->get(\OCP\IConfig::class),
918
-				$this->get(CsrfTokenManager::class),
919
-				$stream
920
-			);
921
-		});
922
-
923
-		$this->registerService(IRequestId::class, function (ContainerInterface $c): IRequestId {
924
-			return new RequestId(
925
-				$_SERVER['UNIQUE_ID'] ?? '',
926
-				$this->get(ISecureRandom::class)
927
-			);
928
-		});
929
-
930
-		$this->registerService(IMailer::class, function (Server $c) {
931
-			return new Mailer(
932
-				$c->get(\OCP\IConfig::class),
933
-				$c->get(LoggerInterface::class),
934
-				$c->get(Defaults::class),
935
-				$c->get(IURLGenerator::class),
936
-				$c->getL10N('lib'),
937
-				$c->get(IEventDispatcher::class),
938
-				$c->get(IFactory::class)
939
-			);
940
-		});
941
-
942
-		/** @since 30.0.0 */
943
-		$this->registerAlias(\OCP\Mail\Provider\IManager::class, \OC\Mail\Provider\Manager::class);
944
-
945
-		$this->registerService(ILDAPProviderFactory::class, function (ContainerInterface $c) {
946
-			$config = $c->get(\OCP\IConfig::class);
947
-			$factoryClass = $config->getSystemValue('ldapProviderFactory', null);
948
-			if (is_null($factoryClass) || !class_exists($factoryClass)) {
949
-				return new NullLDAPProviderFactory($this);
950
-			}
951
-			/** @var \OCP\LDAP\ILDAPProviderFactory $factory */
952
-			return new $factoryClass($this);
953
-		});
954
-		$this->registerService(ILDAPProvider::class, function (ContainerInterface $c) {
955
-			$factory = $c->get(ILDAPProviderFactory::class);
956
-			return $factory->getLDAPProvider();
957
-		});
958
-		$this->registerService(ILockingProvider::class, function (ContainerInterface $c) {
959
-			$ini = $c->get(IniGetWrapper::class);
960
-			$config = $c->get(\OCP\IConfig::class);
961
-			$ttl = $config->getSystemValueInt('filelocking.ttl', max(3600, $ini->getNumeric('max_execution_time')));
962
-			if ($config->getSystemValueBool('filelocking.enabled', true) or (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
963
-				/** @var \OC\Memcache\Factory $memcacheFactory */
964
-				$memcacheFactory = $c->get(ICacheFactory::class);
965
-				$memcache = $memcacheFactory->createLocking('lock');
966
-				if (!($memcache instanceof \OC\Memcache\NullCache)) {
967
-					$timeFactory = $c->get(ITimeFactory::class);
968
-					return new MemcacheLockingProvider($memcache, $timeFactory, $ttl);
969
-				}
970
-				return new DBLockingProvider(
971
-					$c->get(IDBConnection::class),
972
-					new TimeFactory(),
973
-					$ttl,
974
-					!\OC::$CLI
975
-				);
976
-			}
977
-			return new NoopLockingProvider();
978
-		});
979
-
980
-		$this->registerService(ILockManager::class, function (Server $c): LockManager {
981
-			return new LockManager();
982
-		});
983
-
984
-		$this->registerAlias(ILockdownManager::class, 'LockdownManager');
985
-		$this->registerService(SetupManager::class, function ($c) {
986
-			// create the setupmanager through the mount manager to resolve the cyclic dependency
987
-			return $c->get(\OC\Files\Mount\Manager::class)->getSetupManager();
988
-		});
989
-		$this->registerAlias(IMountManager::class, \OC\Files\Mount\Manager::class);
990
-
991
-		$this->registerService(IMimeTypeDetector::class, function (ContainerInterface $c) {
992
-			return new \OC\Files\Type\Detection(
993
-				$c->get(IURLGenerator::class),
994
-				$c->get(LoggerInterface::class),
995
-				\OC::$configDir,
996
-				\OC::$SERVERROOT . '/resources/config/'
997
-			);
998
-		});
999
-
1000
-		$this->registerAlias(IMimeTypeLoader::class, Loader::class);
1001
-		$this->registerService(BundleFetcher::class, function () {
1002
-			return new BundleFetcher($this->getL10N('lib'));
1003
-		});
1004
-		$this->registerAlias(\OCP\Notification\IManager::class, Manager::class);
1005
-
1006
-		$this->registerService(CapabilitiesManager::class, function (ContainerInterface $c) {
1007
-			$manager = new CapabilitiesManager($c->get(LoggerInterface::class));
1008
-			$manager->registerCapability(function () use ($c) {
1009
-				return new \OC\OCS\CoreCapabilities($c->get(\OCP\IConfig::class));
1010
-			});
1011
-			$manager->registerCapability(function () use ($c) {
1012
-				return $c->get(\OC\Security\Bruteforce\Capabilities::class);
1013
-			});
1014
-			return $manager;
1015
-		});
1016
-
1017
-		$this->registerService(ICommentsManager::class, function (Server $c) {
1018
-			$config = $c->get(\OCP\IConfig::class);
1019
-			$factoryClass = $config->getSystemValue('comments.managerFactory', CommentsManagerFactory::class);
1020
-			/** @var \OCP\Comments\ICommentsManagerFactory $factory */
1021
-			$factory = new $factoryClass($this);
1022
-			$manager = $factory->getManager();
1023
-
1024
-			$manager->registerDisplayNameResolver('user', function ($id) use ($c) {
1025
-				$manager = $c->get(IUserManager::class);
1026
-				$userDisplayName = $manager->getDisplayName($id);
1027
-				if ($userDisplayName === null) {
1028
-					$l = $c->get(IFactory::class)->get('core');
1029
-					return $l->t('Unknown account');
1030
-				}
1031
-				return $userDisplayName;
1032
-			});
1033
-
1034
-			return $manager;
1035
-		});
1036
-
1037
-		$this->registerAlias(\OC_Defaults::class, 'ThemingDefaults');
1038
-		$this->registerService('ThemingDefaults', function (Server $c) {
1039
-			try {
1040
-				$classExists = class_exists('OCA\Theming\ThemingDefaults');
1041
-			} catch (\OCP\AutoloadNotAllowedException $e) {
1042
-				// App disabled or in maintenance mode
1043
-				$classExists = false;
1044
-			}
1045
-
1046
-			if ($classExists && $c->get(\OCP\IConfig::class)->getSystemValueBool('installed', false) && $c->get(IAppManager::class)->isEnabledForAnyone('theming') && $c->get(TrustedDomainHelper::class)->isTrustedDomain($c->getRequest()->getInsecureServerHost())) {
1047
-				$backgroundService = new BackgroundService(
1048
-					$c->get(IRootFolder::class),
1049
-					$c->getAppDataDir('theming'),
1050
-					$c->get(IAppConfig::class),
1051
-					$c->get(\OCP\IConfig::class),
1052
-					$c->get(ISession::class)->get('user_id'),
1053
-				);
1054
-				$imageManager = new ImageManager(
1055
-					$c->get(\OCP\IConfig::class),
1056
-					$c->getAppDataDir('theming'),
1057
-					$c->get(IURLGenerator::class),
1058
-					$c->get(ICacheFactory::class),
1059
-					$c->get(LoggerInterface::class),
1060
-					$c->get(ITempManager::class),
1061
-					$backgroundService,
1062
-				);
1063
-				return new ThemingDefaults(
1064
-					$c->get(\OCP\IConfig::class),
1065
-					$c->get(\OCP\IAppConfig::class),
1066
-					$c->getL10N('theming'),
1067
-					$c->get(IUserSession::class),
1068
-					$c->get(IURLGenerator::class),
1069
-					$c->get(ICacheFactory::class),
1070
-					new Util($c->get(ServerVersion::class), $c->get(\OCP\IConfig::class), $this->get(IAppManager::class), $c->getAppDataDir('theming'), $imageManager),
1071
-					$imageManager,
1072
-					$c->get(IAppManager::class),
1073
-					$c->get(INavigationManager::class),
1074
-					$backgroundService,
1075
-				);
1076
-			}
1077
-			return new \OC_Defaults();
1078
-		});
1079
-		$this->registerService(JSCombiner::class, function (Server $c) {
1080
-			return new JSCombiner(
1081
-				$c->getAppDataDir('js'),
1082
-				$c->get(IURLGenerator::class),
1083
-				$this->get(ICacheFactory::class),
1084
-				$c->get(SystemConfig::class),
1085
-				$c->get(LoggerInterface::class)
1086
-			);
1087
-		});
1088
-		$this->registerAlias(\OCP\EventDispatcher\IEventDispatcher::class, \OC\EventDispatcher\EventDispatcher::class);
1089
-
1090
-		$this->registerService('CryptoWrapper', function (ContainerInterface $c) {
1091
-			// FIXME: Instantiated here due to cyclic dependency
1092
-			$request = new Request(
1093
-				[
1094
-					'get' => $_GET,
1095
-					'post' => $_POST,
1096
-					'files' => $_FILES,
1097
-					'server' => $_SERVER,
1098
-					'env' => $_ENV,
1099
-					'cookies' => $_COOKIE,
1100
-					'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
1101
-						? $_SERVER['REQUEST_METHOD']
1102
-						: null,
1103
-				],
1104
-				$c->get(IRequestId::class),
1105
-				$c->get(\OCP\IConfig::class)
1106
-			);
1107
-
1108
-			return new CryptoWrapper(
1109
-				$c->get(ICrypto::class),
1110
-				$c->get(ISecureRandom::class),
1111
-				$request
1112
-			);
1113
-		});
1114
-		$this->registerService(SessionStorage::class, function (ContainerInterface $c) {
1115
-			return new SessionStorage($c->get(ISession::class));
1116
-		});
1117
-		$this->registerAlias(\OCP\Security\IContentSecurityPolicyManager::class, ContentSecurityPolicyManager::class);
1118
-
1119
-		$this->registerService(IProviderFactory::class, function (ContainerInterface $c) {
1120
-			$config = $c->get(\OCP\IConfig::class);
1121
-			$factoryClass = $config->getSystemValue('sharing.managerFactory', ProviderFactory::class);
1122
-			/** @var \OCP\Share\IProviderFactory $factory */
1123
-			return $c->get($factoryClass);
1124
-		});
1125
-
1126
-		$this->registerAlias(\OCP\Share\IManager::class, \OC\Share20\Manager::class);
1127
-
1128
-		$this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function (Server $c) {
1129
-			$instance = new Collaboration\Collaborators\Search($c);
1130
-
1131
-			// register default plugins
1132
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserPlugin::class]);
1133
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => GroupPlugin::class]);
1134
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => MailPlugin::class]);
1135
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => RemotePlugin::class]);
1136
-			$instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE_GROUP', 'class' => RemoteGroupPlugin::class]);
1137
-
1138
-			return $instance;
1139
-		});
1140
-		$this->registerAlias(\OCP\Collaboration\Collaborators\ISearchResult::class, \OC\Collaboration\Collaborators\SearchResult::class);
1141
-
1142
-		$this->registerAlias(\OCP\Collaboration\AutoComplete\IManager::class, \OC\Collaboration\AutoComplete\Manager::class);
1143
-
1144
-		$this->registerAlias(\OCP\Collaboration\Resources\IProviderManager::class, \OC\Collaboration\Resources\ProviderManager::class);
1145
-		$this->registerAlias(\OCP\Collaboration\Resources\IManager::class, \OC\Collaboration\Resources\Manager::class);
1146
-
1147
-		$this->registerAlias(IReferenceManager::class, ReferenceManager::class);
1148
-		$this->registerAlias(ITeamManager::class, TeamManager::class);
1149
-
1150
-		$this->registerDeprecatedAlias('SettingsManager', \OC\Settings\Manager::class);
1151
-		$this->registerAlias(\OCP\Settings\IManager::class, \OC\Settings\Manager::class);
1152
-		$this->registerService(\OC\Files\AppData\Factory::class, function (ContainerInterface $c) {
1153
-			return new \OC\Files\AppData\Factory(
1154
-				$c->get(IRootFolder::class),
1155
-				$c->get(SystemConfig::class)
1156
-			);
1157
-		});
1158
-
1159
-		$this->registerService('LockdownManager', function (ContainerInterface $c) {
1160
-			return new LockdownManager(function () use ($c) {
1161
-				return $c->get(ISession::class);
1162
-			});
1163
-		});
1164
-
1165
-		$this->registerService(\OCP\OCS\IDiscoveryService::class, function (ContainerInterface $c) {
1166
-			return new DiscoveryService(
1167
-				$c->get(ICacheFactory::class),
1168
-				$c->get(IClientService::class)
1169
-			);
1170
-		});
1171
-		$this->registerAlias(IOCMDiscoveryService::class, OCMDiscoveryService::class);
1172
-
1173
-		$this->registerService(ICloudIdManager::class, function (ContainerInterface $c) {
1174
-			return new CloudIdManager(
1175
-				$c->get(\OCP\Contacts\IManager::class),
1176
-				$c->get(IURLGenerator::class),
1177
-				$c->get(IUserManager::class),
1178
-				$c->get(ICacheFactory::class),
1179
-				$c->get(IEventDispatcher::class),
1180
-			);
1181
-		});
1182
-
1183
-		$this->registerAlias(\OCP\GlobalScale\IConfig::class, \OC\GlobalScale\Config::class);
1184
-		$this->registerAlias(ICloudFederationProviderManager::class, CloudFederationProviderManager::class);
1185
-		$this->registerService(ICloudFederationFactory::class, function (Server $c) {
1186
-			return new CloudFederationFactory();
1187
-		});
251
+    /** @var string */
252
+    private $webRoot;
253
+
254
+    /**
255
+     * @param string $webRoot
256
+     * @param \OC\Config $config
257
+     */
258
+    public function __construct($webRoot, \OC\Config $config) {
259
+        parent::__construct();
260
+        $this->webRoot = $webRoot;
261
+
262
+        // To find out if we are running from CLI or not
263
+        $this->registerParameter('isCLI', \OC::$CLI);
264
+        $this->registerParameter('serverRoot', \OC::$SERVERROOT);
265
+
266
+        $this->registerService(ContainerInterface::class, function (ContainerInterface $c) {
267
+            return $c;
268
+        });
269
+        $this->registerService(\OCP\IServerContainer::class, function (ContainerInterface $c) {
270
+            return $c;
271
+        });
272
+
273
+        $this->registerAlias(\OCP\Calendar\IManager::class, \OC\Calendar\Manager::class);
274
+
275
+        $this->registerAlias(\OCP\Calendar\Resource\IManager::class, \OC\Calendar\Resource\Manager::class);
276
+
277
+        $this->registerAlias(\OCP\Calendar\Room\IManager::class, \OC\Calendar\Room\Manager::class);
278
+
279
+        $this->registerAlias(\OCP\Contacts\IManager::class, \OC\ContactsManager::class);
280
+
281
+        $this->registerAlias(\OCP\DirectEditing\IManager::class, \OC\DirectEditing\Manager::class);
282
+        $this->registerAlias(ITemplateManager::class, TemplateManager::class);
283
+        $this->registerAlias(\OCP\Template\ITemplateManager::class, \OC\Template\TemplateManager::class);
284
+
285
+        $this->registerAlias(IActionFactory::class, ActionFactory::class);
286
+
287
+        $this->registerService(View::class, function (Server $c) {
288
+            return new View();
289
+        }, false);
290
+
291
+        $this->registerService(IPreview::class, function (ContainerInterface $c) {
292
+            return new PreviewManager(
293
+                $c->get(\OCP\IConfig::class),
294
+                $c->get(IRootFolder::class),
295
+                new \OC\Preview\Storage\Root(
296
+                    $c->get(IRootFolder::class),
297
+                    $c->get(SystemConfig::class)
298
+                ),
299
+                $c->get(IEventDispatcher::class),
300
+                $c->get(GeneratorHelper::class),
301
+                $c->get(ISession::class)->get('user_id'),
302
+                $c->get(Coordinator::class),
303
+                $c->get(IServerContainer::class),
304
+                $c->get(IBinaryFinder::class),
305
+                $c->get(IMagickSupport::class)
306
+            );
307
+        });
308
+        $this->registerAlias(IMimeIconProvider::class, MimeIconProvider::class);
309
+
310
+        $this->registerService(\OC\Preview\Watcher::class, function (ContainerInterface $c) {
311
+            return new \OC\Preview\Watcher(
312
+                new \OC\Preview\Storage\Root(
313
+                    $c->get(IRootFolder::class),
314
+                    $c->get(SystemConfig::class)
315
+                )
316
+            );
317
+        });
318
+
319
+        $this->registerService(IProfiler::class, function (Server $c) {
320
+            return new Profiler($c->get(SystemConfig::class));
321
+        });
322
+
323
+        $this->registerService(\OCP\Encryption\IManager::class, function (Server $c): Encryption\Manager {
324
+            $view = new View();
325
+            $util = new Encryption\Util(
326
+                $view,
327
+                $c->get(IUserManager::class),
328
+                $c->get(IGroupManager::class),
329
+                $c->get(\OCP\IConfig::class)
330
+            );
331
+            return new Encryption\Manager(
332
+                $c->get(\OCP\IConfig::class),
333
+                $c->get(LoggerInterface::class),
334
+                $c->getL10N('core'),
335
+                new View(),
336
+                $util,
337
+                new ArrayCache()
338
+            );
339
+        });
340
+
341
+        $this->registerService(IFile::class, function (ContainerInterface $c) {
342
+            $util = new Encryption\Util(
343
+                new View(),
344
+                $c->get(IUserManager::class),
345
+                $c->get(IGroupManager::class),
346
+                $c->get(\OCP\IConfig::class)
347
+            );
348
+            return new Encryption\File(
349
+                $util,
350
+                $c->get(IRootFolder::class),
351
+                $c->get(\OCP\Share\IManager::class)
352
+            );
353
+        });
354
+
355
+        $this->registerService(IStorage::class, function (ContainerInterface $c) {
356
+            $view = new View();
357
+            $util = new Encryption\Util(
358
+                $view,
359
+                $c->get(IUserManager::class),
360
+                $c->get(IGroupManager::class),
361
+                $c->get(\OCP\IConfig::class)
362
+            );
363
+
364
+            return new Encryption\Keys\Storage(
365
+                $view,
366
+                $util,
367
+                $c->get(ICrypto::class),
368
+                $c->get(\OCP\IConfig::class)
369
+            );
370
+        });
371
+
372
+        $this->registerAlias(\OCP\ITagManager::class, TagManager::class);
373
+
374
+        $this->registerService('SystemTagManagerFactory', function (ContainerInterface $c) {
375
+            /** @var \OCP\IConfig $config */
376
+            $config = $c->get(\OCP\IConfig::class);
377
+            $factoryClass = $config->getSystemValue('systemtags.managerFactory', SystemTagManagerFactory::class);
378
+            return new $factoryClass($this);
379
+        });
380
+        $this->registerService(ISystemTagManager::class, function (ContainerInterface $c) {
381
+            return $c->get('SystemTagManagerFactory')->getManager();
382
+        });
383
+        /** @deprecated 19.0.0 */
384
+        $this->registerDeprecatedAlias('SystemTagManager', ISystemTagManager::class);
385
+
386
+        $this->registerService(ISystemTagObjectMapper::class, function (ContainerInterface $c) {
387
+            return $c->get('SystemTagManagerFactory')->getObjectMapper();
388
+        });
389
+        $this->registerAlias(IFileAccess::class, FileAccess::class);
390
+        $this->registerService('RootFolder', function (ContainerInterface $c) {
391
+            $manager = \OC\Files\Filesystem::getMountManager();
392
+            $view = new View();
393
+            /** @var IUserSession $userSession */
394
+            $userSession = $c->get(IUserSession::class);
395
+            $root = new Root(
396
+                $manager,
397
+                $view,
398
+                $userSession->getUser(),
399
+                $c->get(IUserMountCache::class),
400
+                $this->get(LoggerInterface::class),
401
+                $this->get(IUserManager::class),
402
+                $this->get(IEventDispatcher::class),
403
+                $this->get(ICacheFactory::class),
404
+            );
405
+
406
+            $previewConnector = new \OC\Preview\WatcherConnector(
407
+                $root,
408
+                $c->get(SystemConfig::class),
409
+                $this->get(IEventDispatcher::class)
410
+            );
411
+            $previewConnector->connectWatcher();
412
+
413
+            return $root;
414
+        });
415
+        $this->registerService(HookConnector::class, function (ContainerInterface $c) {
416
+            return new HookConnector(
417
+                $c->get(IRootFolder::class),
418
+                new View(),
419
+                $c->get(IEventDispatcher::class),
420
+                $c->get(LoggerInterface::class)
421
+            );
422
+        });
423
+
424
+        $this->registerService(IRootFolder::class, function (ContainerInterface $c) {
425
+            return new LazyRoot(function () use ($c) {
426
+                return $c->get('RootFolder');
427
+            });
428
+        });
429
+
430
+        $this->registerAlias(\OCP\IUserManager::class, \OC\User\Manager::class);
431
+
432
+        $this->registerService(DisplayNameCache::class, function (ContainerInterface $c) {
433
+            return $c->get(\OC\User\Manager::class)->getDisplayNameCache();
434
+        });
435
+
436
+        $this->registerService(\OCP\IGroupManager::class, function (ContainerInterface $c) {
437
+            $groupManager = new \OC\Group\Manager(
438
+                $this->get(IUserManager::class),
439
+                $this->get(IEventDispatcher::class),
440
+                $this->get(LoggerInterface::class),
441
+                $this->get(ICacheFactory::class),
442
+                $this->get(IRemoteAddress::class),
443
+            );
444
+            return $groupManager;
445
+        });
446
+
447
+        $this->registerService(Store::class, function (ContainerInterface $c) {
448
+            $session = $c->get(ISession::class);
449
+            if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
450
+                $tokenProvider = $c->get(IProvider::class);
451
+            } else {
452
+                $tokenProvider = null;
453
+            }
454
+            $logger = $c->get(LoggerInterface::class);
455
+            $crypto = $c->get(ICrypto::class);
456
+            return new Store($session, $logger, $crypto, $tokenProvider);
457
+        });
458
+        $this->registerAlias(IStore::class, Store::class);
459
+        $this->registerAlias(IProvider::class, Authentication\Token\Manager::class);
460
+        $this->registerAlias(OCPIProvider::class, Authentication\Token\Manager::class);
461
+
462
+        $this->registerService(\OC\User\Session::class, function (Server $c) {
463
+            $manager = $c->get(IUserManager::class);
464
+            $session = new \OC\Session\Memory();
465
+            $timeFactory = new TimeFactory();
466
+            // Token providers might require a working database. This code
467
+            // might however be called when Nextcloud is not yet setup.
468
+            if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
469
+                $provider = $c->get(IProvider::class);
470
+            } else {
471
+                $provider = null;
472
+            }
473
+
474
+            $userSession = new \OC\User\Session(
475
+                $manager,
476
+                $session,
477
+                $timeFactory,
478
+                $provider,
479
+                $c->get(\OCP\IConfig::class),
480
+                $c->get(ISecureRandom::class),
481
+                $c->get('LockdownManager'),
482
+                $c->get(LoggerInterface::class),
483
+                $c->get(IEventDispatcher::class),
484
+            );
485
+            /** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
486
+            $userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) {
487
+                \OC_Hook::emit('OC_User', 'pre_createUser', ['run' => true, 'uid' => $uid, 'password' => $password]);
488
+            });
489
+            /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
490
+            $userSession->listen('\OC\User', 'postCreateUser', function ($user, $password) {
491
+                /** @var \OC\User\User $user */
492
+                \OC_Hook::emit('OC_User', 'post_createUser', ['uid' => $user->getUID(), 'password' => $password]);
493
+            });
494
+            /** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
495
+            $userSession->listen('\OC\User', 'preDelete', function ($user) {
496
+                /** @var \OC\User\User $user */
497
+                \OC_Hook::emit('OC_User', 'pre_deleteUser', ['run' => true, 'uid' => $user->getUID()]);
498
+            });
499
+            /** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
500
+            $userSession->listen('\OC\User', 'postDelete', function ($user) {
501
+                /** @var \OC\User\User $user */
502
+                \OC_Hook::emit('OC_User', 'post_deleteUser', ['uid' => $user->getUID()]);
503
+            });
504
+            $userSession->listen('\OC\User', 'preSetPassword', function ($user, $password, $recoveryPassword) {
505
+                /** @var \OC\User\User $user */
506
+                \OC_Hook::emit('OC_User', 'pre_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
507
+            });
508
+            $userSession->listen('\OC\User', 'postSetPassword', function ($user, $password, $recoveryPassword) {
509
+                /** @var \OC\User\User $user */
510
+                \OC_Hook::emit('OC_User', 'post_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
511
+            });
512
+            $userSession->listen('\OC\User', 'preLogin', function ($uid, $password) {
513
+                \OC_Hook::emit('OC_User', 'pre_login', ['run' => true, 'uid' => $uid, 'password' => $password]);
514
+
515
+                /** @var IEventDispatcher $dispatcher */
516
+                $dispatcher = $this->get(IEventDispatcher::class);
517
+                $dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password));
518
+            });
519
+            $userSession->listen('\OC\User', 'postLogin', function ($user, $loginName, $password, $isTokenLogin) {
520
+                /** @var \OC\User\User $user */
521
+                \OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'loginName' => $loginName, 'password' => $password, 'isTokenLogin' => $isTokenLogin]);
522
+
523
+                /** @var IEventDispatcher $dispatcher */
524
+                $dispatcher = $this->get(IEventDispatcher::class);
525
+                $dispatcher->dispatchTyped(new UserLoggedInEvent($user, $loginName, $password, $isTokenLogin));
526
+            });
527
+            $userSession->listen('\OC\User', 'preRememberedLogin', function ($uid) {
528
+                /** @var IEventDispatcher $dispatcher */
529
+                $dispatcher = $this->get(IEventDispatcher::class);
530
+                $dispatcher->dispatchTyped(new BeforeUserLoggedInWithCookieEvent($uid));
531
+            });
532
+            $userSession->listen('\OC\User', 'postRememberedLogin', function ($user, $password) {
533
+                /** @var \OC\User\User $user */
534
+                \OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'password' => $password]);
535
+
536
+                /** @var IEventDispatcher $dispatcher */
537
+                $dispatcher = $this->get(IEventDispatcher::class);
538
+                $dispatcher->dispatchTyped(new UserLoggedInWithCookieEvent($user, $password));
539
+            });
540
+            $userSession->listen('\OC\User', 'logout', function ($user) {
541
+                \OC_Hook::emit('OC_User', 'logout', []);
542
+
543
+                /** @var IEventDispatcher $dispatcher */
544
+                $dispatcher = $this->get(IEventDispatcher::class);
545
+                $dispatcher->dispatchTyped(new BeforeUserLoggedOutEvent($user));
546
+            });
547
+            $userSession->listen('\OC\User', 'postLogout', function ($user) {
548
+                /** @var IEventDispatcher $dispatcher */
549
+                $dispatcher = $this->get(IEventDispatcher::class);
550
+                $dispatcher->dispatchTyped(new UserLoggedOutEvent($user));
551
+            });
552
+            $userSession->listen('\OC\User', 'changeUser', function ($user, $feature, $value, $oldValue) {
553
+                /** @var \OC\User\User $user */
554
+                \OC_Hook::emit('OC_User', 'changeUser', ['run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value, 'old_value' => $oldValue]);
555
+            });
556
+            return $userSession;
557
+        });
558
+        $this->registerAlias(\OCP\IUserSession::class, \OC\User\Session::class);
559
+
560
+        $this->registerAlias(\OCP\Authentication\TwoFactorAuth\IRegistry::class, \OC\Authentication\TwoFactorAuth\Registry::class);
561
+
562
+        $this->registerAlias(INavigationManager::class, \OC\NavigationManager::class);
563
+
564
+        $this->registerAlias(\OCP\IConfig::class, \OC\AllConfig::class);
565
+
566
+        $this->registerService(\OC\SystemConfig::class, function ($c) use ($config) {
567
+            return new \OC\SystemConfig($config);
568
+        });
569
+
570
+        $this->registerAlias(IAppConfig::class, \OC\AppConfig::class);
571
+        $this->registerAlias(IUserConfig::class, \OC\Config\UserConfig::class);
572
+
573
+        $this->registerService(IFactory::class, function (Server $c) {
574
+            return new \OC\L10N\Factory(
575
+                $c->get(\OCP\IConfig::class),
576
+                $c->getRequest(),
577
+                $c->get(IUserSession::class),
578
+                $c->get(ICacheFactory::class),
579
+                \OC::$SERVERROOT,
580
+                $c->get(IAppManager::class),
581
+            );
582
+        });
583
+
584
+        $this->registerAlias(IURLGenerator::class, URLGenerator::class);
585
+
586
+        $this->registerService(ICache::class, function ($c) {
587
+            return new Cache\File();
588
+        });
589
+
590
+        $this->registerService(Factory::class, function (Server $c) {
591
+            $profiler = $c->get(IProfiler::class);
592
+            $arrayCacheFactory = new \OC\Memcache\Factory(fn () => '', $c->get(LoggerInterface::class),
593
+                $profiler,
594
+                ArrayCache::class,
595
+                ArrayCache::class,
596
+                ArrayCache::class
597
+            );
598
+            /** @var SystemConfig $config */
599
+            $config = $c->get(SystemConfig::class);
600
+            /** @var ServerVersion $serverVersion */
601
+            $serverVersion = $c->get(ServerVersion::class);
602
+
603
+            if ($config->getValue('installed', false) && !(defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
604
+                $logQuery = $config->getValue('log_query');
605
+                $prefixClosure = function () use ($logQuery, $serverVersion): ?string {
606
+                    if (!$logQuery) {
607
+                        try {
608
+                            $v = \OCP\Server::get(IAppConfig::class)->getAppInstalledVersions(true);
609
+                        } catch (\Doctrine\DBAL\Exception $e) {
610
+                            // Database service probably unavailable
611
+                            // Probably related to https://github.com/nextcloud/server/issues/37424
612
+                            return null;
613
+                        }
614
+                    } else {
615
+                        // If the log_query is enabled, we can not get the app versions
616
+                        // as that does a query, which will be logged and the logging
617
+                        // depends on redis and here we are back again in the same function.
618
+                        $v = [
619
+                            'log_query' => 'enabled',
620
+                        ];
621
+                    }
622
+                    $v['core'] = implode(',', $serverVersion->getVersion());
623
+                    $version = implode(',', array_keys($v)) . implode(',', $v);
624
+                    $instanceId = \OC_Util::getInstanceId();
625
+                    $path = \OC::$SERVERROOT;
626
+                    return md5($instanceId . '-' . $version . '-' . $path);
627
+                };
628
+                return new \OC\Memcache\Factory($prefixClosure,
629
+                    $c->get(LoggerInterface::class),
630
+                    $profiler,
631
+                    /** @psalm-taint-escape callable */
632
+                    $config->getValue('memcache.local', null),
633
+                    /** @psalm-taint-escape callable */
634
+                    $config->getValue('memcache.distributed', null),
635
+                    /** @psalm-taint-escape callable */
636
+                    $config->getValue('memcache.locking', null),
637
+                    /** @psalm-taint-escape callable */
638
+                    $config->getValue('redis_log_file')
639
+                );
640
+            }
641
+            return $arrayCacheFactory;
642
+        });
643
+        $this->registerAlias(ICacheFactory::class, Factory::class);
644
+
645
+        $this->registerService('RedisFactory', function (Server $c) {
646
+            $systemConfig = $c->get(SystemConfig::class);
647
+            return new RedisFactory($systemConfig, $c->get(IEventLogger::class));
648
+        });
649
+
650
+        $this->registerService(\OCP\Activity\IManager::class, function (Server $c) {
651
+            $l10n = $this->get(IFactory::class)->get('lib');
652
+            return new \OC\Activity\Manager(
653
+                $c->getRequest(),
654
+                $c->get(IUserSession::class),
655
+                $c->get(\OCP\IConfig::class),
656
+                $c->get(IValidator::class),
657
+                $c->get(IRichTextFormatter::class),
658
+                $l10n
659
+            );
660
+        });
661
+
662
+        $this->registerService(\OCP\Activity\IEventMerger::class, function (Server $c) {
663
+            return new \OC\Activity\EventMerger(
664
+                $c->getL10N('lib')
665
+            );
666
+        });
667
+        $this->registerAlias(IValidator::class, Validator::class);
668
+
669
+        $this->registerService(AvatarManager::class, function (Server $c) {
670
+            return new AvatarManager(
671
+                $c->get(IUserSession::class),
672
+                $c->get(\OC\User\Manager::class),
673
+                $c->getAppDataDir('avatar'),
674
+                $c->getL10N('lib'),
675
+                $c->get(LoggerInterface::class),
676
+                $c->get(\OCP\IConfig::class),
677
+                $c->get(IAccountManager::class),
678
+                $c->get(KnownUserService::class)
679
+            );
680
+        });
681
+
682
+        $this->registerAlias(IAvatarManager::class, AvatarManager::class);
683
+
684
+        $this->registerAlias(\OCP\Support\CrashReport\IRegistry::class, \OC\Support\CrashReport\Registry::class);
685
+        $this->registerAlias(\OCP\Support\Subscription\IRegistry::class, \OC\Support\Subscription\Registry::class);
686
+        $this->registerAlias(\OCP\Support\Subscription\IAssertion::class, \OC\Support\Subscription\Assertion::class);
687
+
688
+        /** Only used by the PsrLoggerAdapter should not be used by apps */
689
+        $this->registerService(\OC\Log::class, function (Server $c) {
690
+            $logType = $c->get(AllConfig::class)->getSystemValue('log_type', 'file');
691
+            $factory = new LogFactory($c, $this->get(SystemConfig::class));
692
+            $logger = $factory->get($logType);
693
+            $registry = $c->get(\OCP\Support\CrashReport\IRegistry::class);
694
+
695
+            return new Log($logger, $this->get(SystemConfig::class), crashReporters: $registry);
696
+        });
697
+        // PSR-3 logger
698
+        $this->registerAlias(LoggerInterface::class, PsrLoggerAdapter::class);
699
+
700
+        $this->registerService(ILogFactory::class, function (Server $c) {
701
+            return new LogFactory($c, $this->get(SystemConfig::class));
702
+        });
703
+
704
+        $this->registerAlias(IJobList::class, \OC\BackgroundJob\JobList::class);
705
+
706
+        $this->registerService(Router::class, function (Server $c) {
707
+            $cacheFactory = $c->get(ICacheFactory::class);
708
+            if ($cacheFactory->isLocalCacheAvailable()) {
709
+                $router = $c->resolve(CachingRouter::class);
710
+            } else {
711
+                $router = $c->resolve(Router::class);
712
+            }
713
+            return $router;
714
+        });
715
+        $this->registerAlias(IRouter::class, Router::class);
716
+
717
+        $this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function ($c) {
718
+            $config = $c->get(\OCP\IConfig::class);
719
+            if (ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
720
+                $backend = new \OC\Security\RateLimiting\Backend\MemoryCacheBackend(
721
+                    $c->get(AllConfig::class),
722
+                    $this->get(ICacheFactory::class),
723
+                    new \OC\AppFramework\Utility\TimeFactory()
724
+                );
725
+            } else {
726
+                $backend = new \OC\Security\RateLimiting\Backend\DatabaseBackend(
727
+                    $c->get(AllConfig::class),
728
+                    $c->get(IDBConnection::class),
729
+                    new \OC\AppFramework\Utility\TimeFactory()
730
+                );
731
+            }
732
+
733
+            return $backend;
734
+        });
735
+
736
+        $this->registerAlias(\OCP\Security\ISecureRandom::class, SecureRandom::class);
737
+        $this->registerAlias(\OCP\Security\IRemoteHostValidator::class, \OC\Security\RemoteHostValidator::class);
738
+        $this->registerAlias(IVerificationToken::class, VerificationToken::class);
739
+
740
+        $this->registerAlias(ICrypto::class, Crypto::class);
741
+
742
+        $this->registerAlias(IHasher::class, Hasher::class);
743
+
744
+        $this->registerAlias(ICredentialsManager::class, CredentialsManager::class);
745
+
746
+        $this->registerAlias(IDBConnection::class, ConnectionAdapter::class);
747
+        $this->registerService(Connection::class, function (Server $c) {
748
+            $systemConfig = $c->get(SystemConfig::class);
749
+            $factory = new \OC\DB\ConnectionFactory($systemConfig, $c->get(ICacheFactory::class));
750
+            $type = $systemConfig->getValue('dbtype', 'sqlite');
751
+            if (!$factory->isValidType($type)) {
752
+                throw new \OC\DatabaseException('Invalid database type');
753
+            }
754
+            $connection = $factory->getConnection($type, []);
755
+            return $connection;
756
+        });
757
+
758
+        $this->registerAlias(ICertificateManager::class, CertificateManager::class);
759
+        $this->registerAlias(IClientService::class, ClientService::class);
760
+        $this->registerService(NegativeDnsCache::class, function (ContainerInterface $c) {
761
+            return new NegativeDnsCache(
762
+                $c->get(ICacheFactory::class),
763
+            );
764
+        });
765
+        $this->registerDeprecatedAlias('HttpClientService', IClientService::class);
766
+        $this->registerService(IEventLogger::class, function (ContainerInterface $c) {
767
+            return new EventLogger($c->get(SystemConfig::class), $c->get(LoggerInterface::class), $c->get(Log::class));
768
+        });
769
+
770
+        $this->registerService(IQueryLogger::class, function (ContainerInterface $c) {
771
+            $queryLogger = new QueryLogger();
772
+            if ($c->get(SystemConfig::class)->getValue('debug', false)) {
773
+                // In debug mode, module is being activated by default
774
+                $queryLogger->activate();
775
+            }
776
+            return $queryLogger;
777
+        });
778
+
779
+        $this->registerAlias(ITempManager::class, TempManager::class);
780
+
781
+        $this->registerService(AppManager::class, function (ContainerInterface $c) {
782
+            // TODO: use auto-wiring
783
+            return new \OC\App\AppManager(
784
+                $c->get(IUserSession::class),
785
+                $c->get(\OCP\IConfig::class),
786
+                $c->get(IGroupManager::class),
787
+                $c->get(ICacheFactory::class),
788
+                $c->get(IEventDispatcher::class),
789
+                $c->get(LoggerInterface::class),
790
+                $c->get(ServerVersion::class),
791
+            );
792
+        });
793
+        $this->registerAlias(IAppManager::class, AppManager::class);
794
+
795
+        $this->registerAlias(IDateTimeZone::class, DateTimeZone::class);
796
+
797
+        $this->registerService(IDateTimeFormatter::class, function (Server $c) {
798
+            $language = $c->get(\OCP\IConfig::class)->getUserValue($c->get(ISession::class)->get('user_id'), 'core', 'lang', null);
799
+
800
+            return new DateTimeFormatter(
801
+                $c->get(IDateTimeZone::class)->getTimeZone(),
802
+                $c->getL10N('lib', $language)
803
+            );
804
+        });
805
+
806
+        $this->registerService(IUserMountCache::class, function (ContainerInterface $c) {
807
+            $mountCache = $c->get(UserMountCache::class);
808
+            $listener = new UserMountCacheListener($mountCache);
809
+            $listener->listen($c->get(IUserManager::class));
810
+            return $mountCache;
811
+        });
812
+
813
+        $this->registerService(IMountProviderCollection::class, function (ContainerInterface $c) {
814
+            $loader = $c->get(IStorageFactory::class);
815
+            $mountCache = $c->get(IUserMountCache::class);
816
+            $eventLogger = $c->get(IEventLogger::class);
817
+            $manager = new MountProviderCollection($loader, $mountCache, $eventLogger);
818
+
819
+            // builtin providers
820
+
821
+            $config = $c->get(\OCP\IConfig::class);
822
+            $logger = $c->get(LoggerInterface::class);
823
+            $objectStoreConfig = $c->get(PrimaryObjectStoreConfig::class);
824
+            $manager->registerProvider(new CacheMountProvider($config));
825
+            $manager->registerHomeProvider(new LocalHomeMountProvider());
826
+            $manager->registerHomeProvider(new ObjectHomeMountProvider($objectStoreConfig));
827
+            $manager->registerRootProvider(new RootMountProvider($objectStoreConfig, $config));
828
+            $manager->registerRootProvider(new ObjectStorePreviewCacheMountProvider($logger, $config));
829
+
830
+            return $manager;
831
+        });
832
+
833
+        $this->registerService(IBus::class, function (ContainerInterface $c) {
834
+            $busClass = $c->get(\OCP\IConfig::class)->getSystemValueString('commandbus');
835
+            if ($busClass) {
836
+                [$app, $class] = explode('::', $busClass, 2);
837
+                if ($c->get(IAppManager::class)->isEnabledForUser($app)) {
838
+                    $c->get(IAppManager::class)->loadApp($app);
839
+                    return $c->get($class);
840
+                } else {
841
+                    throw new ServiceUnavailableException("The app providing the command bus ($app) is not enabled");
842
+                }
843
+            } else {
844
+                $jobList = $c->get(IJobList::class);
845
+                return new CronBus($jobList);
846
+            }
847
+        });
848
+        $this->registerDeprecatedAlias('AsyncCommandBus', IBus::class);
849
+        $this->registerAlias(ITrustedDomainHelper::class, TrustedDomainHelper::class);
850
+        $this->registerAlias(IThrottler::class, Throttler::class);
851
+
852
+        $this->registerService(\OC\Security\Bruteforce\Backend\IBackend::class, function ($c) {
853
+            $config = $c->get(\OCP\IConfig::class);
854
+            if (!$config->getSystemValueBool('auth.bruteforce.protection.force.database', false)
855
+                && ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
856
+                $backend = $c->get(\OC\Security\Bruteforce\Backend\MemoryCacheBackend::class);
857
+            } else {
858
+                $backend = $c->get(\OC\Security\Bruteforce\Backend\DatabaseBackend::class);
859
+            }
860
+
861
+            return $backend;
862
+        });
863
+
864
+        $this->registerDeprecatedAlias('IntegrityCodeChecker', Checker::class);
865
+        $this->registerService(Checker::class, function (ContainerInterface $c) {
866
+            // IConfig requires a working database. This code
867
+            // might however be called when Nextcloud is not yet setup.
868
+            if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
869
+                $config = $c->get(\OCP\IConfig::class);
870
+                $appConfig = $c->get(\OCP\IAppConfig::class);
871
+            } else {
872
+                $config = null;
873
+                $appConfig = null;
874
+            }
875
+
876
+            return new Checker(
877
+                $c->get(ServerVersion::class),
878
+                $c->get(EnvironmentHelper::class),
879
+                new FileAccessHelper(),
880
+                new AppLocator(),
881
+                $config,
882
+                $appConfig,
883
+                $c->get(ICacheFactory::class),
884
+                $c->get(IAppManager::class),
885
+                $c->get(IMimeTypeDetector::class)
886
+            );
887
+        });
888
+        $this->registerService(\OCP\IRequest::class, function (ContainerInterface $c) {
889
+            if (isset($this['urlParams'])) {
890
+                $urlParams = $this['urlParams'];
891
+            } else {
892
+                $urlParams = [];
893
+            }
894
+
895
+            if (defined('PHPUNIT_RUN') && PHPUNIT_RUN
896
+                && in_array('fakeinput', stream_get_wrappers())
897
+            ) {
898
+                $stream = 'fakeinput://data';
899
+            } else {
900
+                $stream = 'php://input';
901
+            }
902
+
903
+            return new Request(
904
+                [
905
+                    'get' => $_GET,
906
+                    'post' => $_POST,
907
+                    'files' => $_FILES,
908
+                    'server' => $_SERVER,
909
+                    'env' => $_ENV,
910
+                    'cookies' => $_COOKIE,
911
+                    'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
912
+                        ? $_SERVER['REQUEST_METHOD']
913
+                        : '',
914
+                    'urlParams' => $urlParams,
915
+                ],
916
+                $this->get(IRequestId::class),
917
+                $this->get(\OCP\IConfig::class),
918
+                $this->get(CsrfTokenManager::class),
919
+                $stream
920
+            );
921
+        });
922
+
923
+        $this->registerService(IRequestId::class, function (ContainerInterface $c): IRequestId {
924
+            return new RequestId(
925
+                $_SERVER['UNIQUE_ID'] ?? '',
926
+                $this->get(ISecureRandom::class)
927
+            );
928
+        });
929
+
930
+        $this->registerService(IMailer::class, function (Server $c) {
931
+            return new Mailer(
932
+                $c->get(\OCP\IConfig::class),
933
+                $c->get(LoggerInterface::class),
934
+                $c->get(Defaults::class),
935
+                $c->get(IURLGenerator::class),
936
+                $c->getL10N('lib'),
937
+                $c->get(IEventDispatcher::class),
938
+                $c->get(IFactory::class)
939
+            );
940
+        });
941
+
942
+        /** @since 30.0.0 */
943
+        $this->registerAlias(\OCP\Mail\Provider\IManager::class, \OC\Mail\Provider\Manager::class);
944
+
945
+        $this->registerService(ILDAPProviderFactory::class, function (ContainerInterface $c) {
946
+            $config = $c->get(\OCP\IConfig::class);
947
+            $factoryClass = $config->getSystemValue('ldapProviderFactory', null);
948
+            if (is_null($factoryClass) || !class_exists($factoryClass)) {
949
+                return new NullLDAPProviderFactory($this);
950
+            }
951
+            /** @var \OCP\LDAP\ILDAPProviderFactory $factory */
952
+            return new $factoryClass($this);
953
+        });
954
+        $this->registerService(ILDAPProvider::class, function (ContainerInterface $c) {
955
+            $factory = $c->get(ILDAPProviderFactory::class);
956
+            return $factory->getLDAPProvider();
957
+        });
958
+        $this->registerService(ILockingProvider::class, function (ContainerInterface $c) {
959
+            $ini = $c->get(IniGetWrapper::class);
960
+            $config = $c->get(\OCP\IConfig::class);
961
+            $ttl = $config->getSystemValueInt('filelocking.ttl', max(3600, $ini->getNumeric('max_execution_time')));
962
+            if ($config->getSystemValueBool('filelocking.enabled', true) or (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
963
+                /** @var \OC\Memcache\Factory $memcacheFactory */
964
+                $memcacheFactory = $c->get(ICacheFactory::class);
965
+                $memcache = $memcacheFactory->createLocking('lock');
966
+                if (!($memcache instanceof \OC\Memcache\NullCache)) {
967
+                    $timeFactory = $c->get(ITimeFactory::class);
968
+                    return new MemcacheLockingProvider($memcache, $timeFactory, $ttl);
969
+                }
970
+                return new DBLockingProvider(
971
+                    $c->get(IDBConnection::class),
972
+                    new TimeFactory(),
973
+                    $ttl,
974
+                    !\OC::$CLI
975
+                );
976
+            }
977
+            return new NoopLockingProvider();
978
+        });
979
+
980
+        $this->registerService(ILockManager::class, function (Server $c): LockManager {
981
+            return new LockManager();
982
+        });
983
+
984
+        $this->registerAlias(ILockdownManager::class, 'LockdownManager');
985
+        $this->registerService(SetupManager::class, function ($c) {
986
+            // create the setupmanager through the mount manager to resolve the cyclic dependency
987
+            return $c->get(\OC\Files\Mount\Manager::class)->getSetupManager();
988
+        });
989
+        $this->registerAlias(IMountManager::class, \OC\Files\Mount\Manager::class);
990
+
991
+        $this->registerService(IMimeTypeDetector::class, function (ContainerInterface $c) {
992
+            return new \OC\Files\Type\Detection(
993
+                $c->get(IURLGenerator::class),
994
+                $c->get(LoggerInterface::class),
995
+                \OC::$configDir,
996
+                \OC::$SERVERROOT . '/resources/config/'
997
+            );
998
+        });
999
+
1000
+        $this->registerAlias(IMimeTypeLoader::class, Loader::class);
1001
+        $this->registerService(BundleFetcher::class, function () {
1002
+            return new BundleFetcher($this->getL10N('lib'));
1003
+        });
1004
+        $this->registerAlias(\OCP\Notification\IManager::class, Manager::class);
1005
+
1006
+        $this->registerService(CapabilitiesManager::class, function (ContainerInterface $c) {
1007
+            $manager = new CapabilitiesManager($c->get(LoggerInterface::class));
1008
+            $manager->registerCapability(function () use ($c) {
1009
+                return new \OC\OCS\CoreCapabilities($c->get(\OCP\IConfig::class));
1010
+            });
1011
+            $manager->registerCapability(function () use ($c) {
1012
+                return $c->get(\OC\Security\Bruteforce\Capabilities::class);
1013
+            });
1014
+            return $manager;
1015
+        });
1016
+
1017
+        $this->registerService(ICommentsManager::class, function (Server $c) {
1018
+            $config = $c->get(\OCP\IConfig::class);
1019
+            $factoryClass = $config->getSystemValue('comments.managerFactory', CommentsManagerFactory::class);
1020
+            /** @var \OCP\Comments\ICommentsManagerFactory $factory */
1021
+            $factory = new $factoryClass($this);
1022
+            $manager = $factory->getManager();
1023
+
1024
+            $manager->registerDisplayNameResolver('user', function ($id) use ($c) {
1025
+                $manager = $c->get(IUserManager::class);
1026
+                $userDisplayName = $manager->getDisplayName($id);
1027
+                if ($userDisplayName === null) {
1028
+                    $l = $c->get(IFactory::class)->get('core');
1029
+                    return $l->t('Unknown account');
1030
+                }
1031
+                return $userDisplayName;
1032
+            });
1033
+
1034
+            return $manager;
1035
+        });
1036
+
1037
+        $this->registerAlias(\OC_Defaults::class, 'ThemingDefaults');
1038
+        $this->registerService('ThemingDefaults', function (Server $c) {
1039
+            try {
1040
+                $classExists = class_exists('OCA\Theming\ThemingDefaults');
1041
+            } catch (\OCP\AutoloadNotAllowedException $e) {
1042
+                // App disabled or in maintenance mode
1043
+                $classExists = false;
1044
+            }
1045
+
1046
+            if ($classExists && $c->get(\OCP\IConfig::class)->getSystemValueBool('installed', false) && $c->get(IAppManager::class)->isEnabledForAnyone('theming') && $c->get(TrustedDomainHelper::class)->isTrustedDomain($c->getRequest()->getInsecureServerHost())) {
1047
+                $backgroundService = new BackgroundService(
1048
+                    $c->get(IRootFolder::class),
1049
+                    $c->getAppDataDir('theming'),
1050
+                    $c->get(IAppConfig::class),
1051
+                    $c->get(\OCP\IConfig::class),
1052
+                    $c->get(ISession::class)->get('user_id'),
1053
+                );
1054
+                $imageManager = new ImageManager(
1055
+                    $c->get(\OCP\IConfig::class),
1056
+                    $c->getAppDataDir('theming'),
1057
+                    $c->get(IURLGenerator::class),
1058
+                    $c->get(ICacheFactory::class),
1059
+                    $c->get(LoggerInterface::class),
1060
+                    $c->get(ITempManager::class),
1061
+                    $backgroundService,
1062
+                );
1063
+                return new ThemingDefaults(
1064
+                    $c->get(\OCP\IConfig::class),
1065
+                    $c->get(\OCP\IAppConfig::class),
1066
+                    $c->getL10N('theming'),
1067
+                    $c->get(IUserSession::class),
1068
+                    $c->get(IURLGenerator::class),
1069
+                    $c->get(ICacheFactory::class),
1070
+                    new Util($c->get(ServerVersion::class), $c->get(\OCP\IConfig::class), $this->get(IAppManager::class), $c->getAppDataDir('theming'), $imageManager),
1071
+                    $imageManager,
1072
+                    $c->get(IAppManager::class),
1073
+                    $c->get(INavigationManager::class),
1074
+                    $backgroundService,
1075
+                );
1076
+            }
1077
+            return new \OC_Defaults();
1078
+        });
1079
+        $this->registerService(JSCombiner::class, function (Server $c) {
1080
+            return new JSCombiner(
1081
+                $c->getAppDataDir('js'),
1082
+                $c->get(IURLGenerator::class),
1083
+                $this->get(ICacheFactory::class),
1084
+                $c->get(SystemConfig::class),
1085
+                $c->get(LoggerInterface::class)
1086
+            );
1087
+        });
1088
+        $this->registerAlias(\OCP\EventDispatcher\IEventDispatcher::class, \OC\EventDispatcher\EventDispatcher::class);
1089
+
1090
+        $this->registerService('CryptoWrapper', function (ContainerInterface $c) {
1091
+            // FIXME: Instantiated here due to cyclic dependency
1092
+            $request = new Request(
1093
+                [
1094
+                    'get' => $_GET,
1095
+                    'post' => $_POST,
1096
+                    'files' => $_FILES,
1097
+                    'server' => $_SERVER,
1098
+                    'env' => $_ENV,
1099
+                    'cookies' => $_COOKIE,
1100
+                    'method' => (isset($_SERVER) && isset($_SERVER['REQUEST_METHOD']))
1101
+                        ? $_SERVER['REQUEST_METHOD']
1102
+                        : null,
1103
+                ],
1104
+                $c->get(IRequestId::class),
1105
+                $c->get(\OCP\IConfig::class)
1106
+            );
1107
+
1108
+            return new CryptoWrapper(
1109
+                $c->get(ICrypto::class),
1110
+                $c->get(ISecureRandom::class),
1111
+                $request
1112
+            );
1113
+        });
1114
+        $this->registerService(SessionStorage::class, function (ContainerInterface $c) {
1115
+            return new SessionStorage($c->get(ISession::class));
1116
+        });
1117
+        $this->registerAlias(\OCP\Security\IContentSecurityPolicyManager::class, ContentSecurityPolicyManager::class);
1118
+
1119
+        $this->registerService(IProviderFactory::class, function (ContainerInterface $c) {
1120
+            $config = $c->get(\OCP\IConfig::class);
1121
+            $factoryClass = $config->getSystemValue('sharing.managerFactory', ProviderFactory::class);
1122
+            /** @var \OCP\Share\IProviderFactory $factory */
1123
+            return $c->get($factoryClass);
1124
+        });
1125
+
1126
+        $this->registerAlias(\OCP\Share\IManager::class, \OC\Share20\Manager::class);
1127
+
1128
+        $this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function (Server $c) {
1129
+            $instance = new Collaboration\Collaborators\Search($c);
1130
+
1131
+            // register default plugins
1132
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_USER', 'class' => UserPlugin::class]);
1133
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_GROUP', 'class' => GroupPlugin::class]);
1134
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_EMAIL', 'class' => MailPlugin::class]);
1135
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE', 'class' => RemotePlugin::class]);
1136
+            $instance->registerPlugin(['shareType' => 'SHARE_TYPE_REMOTE_GROUP', 'class' => RemoteGroupPlugin::class]);
1137
+
1138
+            return $instance;
1139
+        });
1140
+        $this->registerAlias(\OCP\Collaboration\Collaborators\ISearchResult::class, \OC\Collaboration\Collaborators\SearchResult::class);
1141
+
1142
+        $this->registerAlias(\OCP\Collaboration\AutoComplete\IManager::class, \OC\Collaboration\AutoComplete\Manager::class);
1143
+
1144
+        $this->registerAlias(\OCP\Collaboration\Resources\IProviderManager::class, \OC\Collaboration\Resources\ProviderManager::class);
1145
+        $this->registerAlias(\OCP\Collaboration\Resources\IManager::class, \OC\Collaboration\Resources\Manager::class);
1146
+
1147
+        $this->registerAlias(IReferenceManager::class, ReferenceManager::class);
1148
+        $this->registerAlias(ITeamManager::class, TeamManager::class);
1149
+
1150
+        $this->registerDeprecatedAlias('SettingsManager', \OC\Settings\Manager::class);
1151
+        $this->registerAlias(\OCP\Settings\IManager::class, \OC\Settings\Manager::class);
1152
+        $this->registerService(\OC\Files\AppData\Factory::class, function (ContainerInterface $c) {
1153
+            return new \OC\Files\AppData\Factory(
1154
+                $c->get(IRootFolder::class),
1155
+                $c->get(SystemConfig::class)
1156
+            );
1157
+        });
1158
+
1159
+        $this->registerService('LockdownManager', function (ContainerInterface $c) {
1160
+            return new LockdownManager(function () use ($c) {
1161
+                return $c->get(ISession::class);
1162
+            });
1163
+        });
1164
+
1165
+        $this->registerService(\OCP\OCS\IDiscoveryService::class, function (ContainerInterface $c) {
1166
+            return new DiscoveryService(
1167
+                $c->get(ICacheFactory::class),
1168
+                $c->get(IClientService::class)
1169
+            );
1170
+        });
1171
+        $this->registerAlias(IOCMDiscoveryService::class, OCMDiscoveryService::class);
1172
+
1173
+        $this->registerService(ICloudIdManager::class, function (ContainerInterface $c) {
1174
+            return new CloudIdManager(
1175
+                $c->get(\OCP\Contacts\IManager::class),
1176
+                $c->get(IURLGenerator::class),
1177
+                $c->get(IUserManager::class),
1178
+                $c->get(ICacheFactory::class),
1179
+                $c->get(IEventDispatcher::class),
1180
+            );
1181
+        });
1182
+
1183
+        $this->registerAlias(\OCP\GlobalScale\IConfig::class, \OC\GlobalScale\Config::class);
1184
+        $this->registerAlias(ICloudFederationProviderManager::class, CloudFederationProviderManager::class);
1185
+        $this->registerService(ICloudFederationFactory::class, function (Server $c) {
1186
+            return new CloudFederationFactory();
1187
+        });
1188 1188
 
1189
-		$this->registerAlias(\OCP\AppFramework\Utility\IControllerMethodReflector::class, \OC\AppFramework\Utility\ControllerMethodReflector::class);
1189
+        $this->registerAlias(\OCP\AppFramework\Utility\IControllerMethodReflector::class, \OC\AppFramework\Utility\ControllerMethodReflector::class);
1190 1190
 
1191
-		$this->registerAlias(\OCP\AppFramework\Utility\ITimeFactory::class, \OC\AppFramework\Utility\TimeFactory::class);
1192
-		$this->registerAlias(\Psr\Clock\ClockInterface::class, \OCP\AppFramework\Utility\ITimeFactory::class);
1191
+        $this->registerAlias(\OCP\AppFramework\Utility\ITimeFactory::class, \OC\AppFramework\Utility\TimeFactory::class);
1192
+        $this->registerAlias(\Psr\Clock\ClockInterface::class, \OCP\AppFramework\Utility\ITimeFactory::class);
1193 1193
 
1194
-		$this->registerService(Defaults::class, function (Server $c) {
1195
-			return new Defaults(
1196
-				$c->get('ThemingDefaults')
1197
-			);
1198
-		});
1194
+        $this->registerService(Defaults::class, function (Server $c) {
1195
+            return new Defaults(
1196
+                $c->get('ThemingDefaults')
1197
+            );
1198
+        });
1199 1199
 
1200
-		$this->registerService(\OCP\ISession::class, function (ContainerInterface $c) {
1201
-			return $c->get(\OCP\IUserSession::class)->getSession();
1202
-		}, false);
1200
+        $this->registerService(\OCP\ISession::class, function (ContainerInterface $c) {
1201
+            return $c->get(\OCP\IUserSession::class)->getSession();
1202
+        }, false);
1203 1203
 
1204
-		$this->registerService(IShareHelper::class, function (ContainerInterface $c) {
1205
-			return new ShareHelper(
1206
-				$c->get(\OCP\Share\IManager::class)
1207
-			);
1208
-		});
1204
+        $this->registerService(IShareHelper::class, function (ContainerInterface $c) {
1205
+            return new ShareHelper(
1206
+                $c->get(\OCP\Share\IManager::class)
1207
+            );
1208
+        });
1209 1209
 
1210
-		$this->registerService(Installer::class, function (ContainerInterface $c) {
1211
-			return new Installer(
1212
-				$c->get(AppFetcher::class),
1213
-				$c->get(IClientService::class),
1214
-				$c->get(ITempManager::class),
1215
-				$c->get(LoggerInterface::class),
1216
-				$c->get(\OCP\IConfig::class),
1217
-				\OC::$CLI
1218
-			);
1219
-		});
1210
+        $this->registerService(Installer::class, function (ContainerInterface $c) {
1211
+            return new Installer(
1212
+                $c->get(AppFetcher::class),
1213
+                $c->get(IClientService::class),
1214
+                $c->get(ITempManager::class),
1215
+                $c->get(LoggerInterface::class),
1216
+                $c->get(\OCP\IConfig::class),
1217
+                \OC::$CLI
1218
+            );
1219
+        });
1220 1220
 
1221
-		$this->registerService(IApiFactory::class, function (ContainerInterface $c) {
1222
-			return new ApiFactory($c->get(IClientService::class));
1223
-		});
1221
+        $this->registerService(IApiFactory::class, function (ContainerInterface $c) {
1222
+            return new ApiFactory($c->get(IClientService::class));
1223
+        });
1224 1224
 
1225
-		$this->registerService(IInstanceFactory::class, function (ContainerInterface $c) {
1226
-			$memcacheFactory = $c->get(ICacheFactory::class);
1227
-			return new InstanceFactory($memcacheFactory->createLocal('remoteinstance.'), $c->get(IClientService::class));
1228
-		});
1225
+        $this->registerService(IInstanceFactory::class, function (ContainerInterface $c) {
1226
+            $memcacheFactory = $c->get(ICacheFactory::class);
1227
+            return new InstanceFactory($memcacheFactory->createLocal('remoteinstance.'), $c->get(IClientService::class));
1228
+        });
1229 1229
 
1230
-		$this->registerAlias(IContactsStore::class, ContactsStore::class);
1231
-		$this->registerAlias(IAccountManager::class, AccountManager::class);
1230
+        $this->registerAlias(IContactsStore::class, ContactsStore::class);
1231
+        $this->registerAlias(IAccountManager::class, AccountManager::class);
1232 1232
 
1233
-		$this->registerAlias(IStorageFactory::class, StorageFactory::class);
1233
+        $this->registerAlias(IStorageFactory::class, StorageFactory::class);
1234 1234
 
1235
-		$this->registerAlias(\OCP\Dashboard\IManager::class, \OC\Dashboard\Manager::class);
1235
+        $this->registerAlias(\OCP\Dashboard\IManager::class, \OC\Dashboard\Manager::class);
1236 1236
 
1237
-		$this->registerAlias(IFullTextSearchManager::class, FullTextSearchManager::class);
1238
-		$this->registerAlias(IFilesMetadataManager::class, FilesMetadataManager::class);
1237
+        $this->registerAlias(IFullTextSearchManager::class, FullTextSearchManager::class);
1238
+        $this->registerAlias(IFilesMetadataManager::class, FilesMetadataManager::class);
1239 1239
 
1240
-		$this->registerAlias(ISubAdmin::class, SubAdmin::class);
1240
+        $this->registerAlias(ISubAdmin::class, SubAdmin::class);
1241 1241
 
1242
-		$this->registerAlias(IInitialStateService::class, InitialStateService::class);
1242
+        $this->registerAlias(IInitialStateService::class, InitialStateService::class);
1243 1243
 
1244
-		$this->registerAlias(\OCP\IEmojiHelper::class, \OC\EmojiHelper::class);
1244
+        $this->registerAlias(\OCP\IEmojiHelper::class, \OC\EmojiHelper::class);
1245 1245
 
1246
-		$this->registerAlias(\OCP\UserStatus\IManager::class, \OC\UserStatus\Manager::class);
1246
+        $this->registerAlias(\OCP\UserStatus\IManager::class, \OC\UserStatus\Manager::class);
1247 1247
 
1248
-		$this->registerAlias(IBroker::class, Broker::class);
1248
+        $this->registerAlias(IBroker::class, Broker::class);
1249 1249
 
1250
-		$this->registerAlias(\OCP\Files\AppData\IAppDataFactory::class, \OC\Files\AppData\Factory::class);
1250
+        $this->registerAlias(\OCP\Files\AppData\IAppDataFactory::class, \OC\Files\AppData\Factory::class);
1251 1251
 
1252
-		$this->registerAlias(\OCP\Files\IFilenameValidator::class, \OC\Files\FilenameValidator::class);
1252
+        $this->registerAlias(\OCP\Files\IFilenameValidator::class, \OC\Files\FilenameValidator::class);
1253 1253
 
1254
-		$this->registerAlias(IBinaryFinder::class, BinaryFinder::class);
1254
+        $this->registerAlias(IBinaryFinder::class, BinaryFinder::class);
1255 1255
 
1256
-		$this->registerAlias(\OCP\Share\IPublicShareTemplateFactory::class, \OC\Share20\PublicShareTemplateFactory::class);
1256
+        $this->registerAlias(\OCP\Share\IPublicShareTemplateFactory::class, \OC\Share20\PublicShareTemplateFactory::class);
1257 1257
 
1258
-		$this->registerAlias(ITranslationManager::class, TranslationManager::class);
1258
+        $this->registerAlias(ITranslationManager::class, TranslationManager::class);
1259 1259
 
1260
-		$this->registerAlias(IConversionManager::class, ConversionManager::class);
1260
+        $this->registerAlias(IConversionManager::class, ConversionManager::class);
1261 1261
 
1262
-		$this->registerAlias(ISpeechToTextManager::class, SpeechToTextManager::class);
1262
+        $this->registerAlias(ISpeechToTextManager::class, SpeechToTextManager::class);
1263 1263
 
1264
-		$this->registerAlias(IEventSourceFactory::class, EventSourceFactory::class);
1264
+        $this->registerAlias(IEventSourceFactory::class, EventSourceFactory::class);
1265 1265
 
1266
-		$this->registerAlias(\OCP\TextProcessing\IManager::class, \OC\TextProcessing\Manager::class);
1266
+        $this->registerAlias(\OCP\TextProcessing\IManager::class, \OC\TextProcessing\Manager::class);
1267 1267
 
1268
-		$this->registerAlias(\OCP\TextToImage\IManager::class, \OC\TextToImage\Manager::class);
1268
+        $this->registerAlias(\OCP\TextToImage\IManager::class, \OC\TextToImage\Manager::class);
1269 1269
 
1270
-		$this->registerAlias(ILimiter::class, Limiter::class);
1270
+        $this->registerAlias(ILimiter::class, Limiter::class);
1271 1271
 
1272
-		$this->registerAlias(IPhoneNumberUtil::class, PhoneNumberUtil::class);
1272
+        $this->registerAlias(IPhoneNumberUtil::class, PhoneNumberUtil::class);
1273 1273
 
1274
-		$this->registerAlias(IOCMProvider::class, OCMProvider::class);
1274
+        $this->registerAlias(IOCMProvider::class, OCMProvider::class);
1275 1275
 
1276
-		$this->registerAlias(ISetupCheckManager::class, SetupCheckManager::class);
1276
+        $this->registerAlias(ISetupCheckManager::class, SetupCheckManager::class);
1277 1277
 
1278
-		$this->registerAlias(IProfileManager::class, ProfileManager::class);
1278
+        $this->registerAlias(IProfileManager::class, ProfileManager::class);
1279 1279
 
1280
-		$this->registerAlias(IAvailabilityCoordinator::class, AvailabilityCoordinator::class);
1280
+        $this->registerAlias(IAvailabilityCoordinator::class, AvailabilityCoordinator::class);
1281 1281
 
1282
-		$this->registerAlias(IDeclarativeManager::class, DeclarativeManager::class);
1282
+        $this->registerAlias(IDeclarativeManager::class, DeclarativeManager::class);
1283 1283
 
1284
-		$this->registerAlias(\OCP\TaskProcessing\IManager::class, \OC\TaskProcessing\Manager::class);
1284
+        $this->registerAlias(\OCP\TaskProcessing\IManager::class, \OC\TaskProcessing\Manager::class);
1285 1285
 
1286
-		$this->registerAlias(IRemoteAddress::class, RemoteAddress::class);
1286
+        $this->registerAlias(IRemoteAddress::class, RemoteAddress::class);
1287 1287
 
1288
-		$this->registerAlias(\OCP\Security\Ip\IFactory::class, \OC\Security\Ip\Factory::class);
1289
-
1290
-		$this->registerAlias(IRichTextFormatter::class, \OC\RichObjectStrings\RichTextFormatter::class);
1291
-
1292
-		$this->registerAlias(ISignatureManager::class, SignatureManager::class);
1293
-
1294
-		$this->connectDispatcher();
1295
-	}
1296
-
1297
-	public function boot() {
1298
-		/** @var HookConnector $hookConnector */
1299
-		$hookConnector = $this->get(HookConnector::class);
1300
-		$hookConnector->viewToNode();
1301
-	}
1302
-
1303
-	private function connectDispatcher(): void {
1304
-		/** @var IEventDispatcher $eventDispatcher */
1305
-		$eventDispatcher = $this->get(IEventDispatcher::class);
1306
-		$eventDispatcher->addServiceListener(LoginFailed::class, LoginFailedListener::class);
1307
-		$eventDispatcher->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class);
1308
-		$eventDispatcher->addServiceListener(UserChangedEvent::class, UserChangedListener::class);
1309
-		$eventDispatcher->addServiceListener(BeforeUserDeletedEvent::class, BeforeUserDeletedListener::class);
1310
-
1311
-		FilesMetadataManager::loadListeners($eventDispatcher);
1312
-		GenerateBlurhashMetadata::loadListeners($eventDispatcher);
1313
-	}
1314
-
1315
-	/**
1316
-	 * @return \OCP\Contacts\IManager
1317
-	 * @deprecated 20.0.0
1318
-	 */
1319
-	public function getContactsManager() {
1320
-		return $this->get(\OCP\Contacts\IManager::class);
1321
-	}
1322
-
1323
-	/**
1324
-	 * @return \OC\Encryption\Manager
1325
-	 * @deprecated 20.0.0
1326
-	 */
1327
-	public function getEncryptionManager() {
1328
-		return $this->get(\OCP\Encryption\IManager::class);
1329
-	}
1330
-
1331
-	/**
1332
-	 * @return \OC\Encryption\File
1333
-	 * @deprecated 20.0.0
1334
-	 */
1335
-	public function getEncryptionFilesHelper() {
1336
-		return $this->get(IFile::class);
1337
-	}
1338
-
1339
-	/**
1340
-	 * The current request object holding all information about the request
1341
-	 * currently being processed is returned from this method.
1342
-	 * In case the current execution was not initiated by a web request null is returned
1343
-	 *
1344
-	 * @return \OCP\IRequest
1345
-	 * @deprecated 20.0.0
1346
-	 */
1347
-	public function getRequest() {
1348
-		return $this->get(IRequest::class);
1349
-	}
1350
-
1351
-	/**
1352
-	 * Returns the root folder of ownCloud's data directory
1353
-	 *
1354
-	 * @return IRootFolder
1355
-	 * @deprecated 20.0.0
1356
-	 */
1357
-	public function getRootFolder() {
1358
-		return $this->get(IRootFolder::class);
1359
-	}
1360
-
1361
-	/**
1362
-	 * Returns the root folder of ownCloud's data directory
1363
-	 * This is the lazy variant so this gets only initialized once it
1364
-	 * is actually used.
1365
-	 *
1366
-	 * @return IRootFolder
1367
-	 * @deprecated 20.0.0
1368
-	 */
1369
-	public function getLazyRootFolder() {
1370
-		return $this->get(IRootFolder::class);
1371
-	}
1372
-
1373
-	/**
1374
-	 * Returns a view to ownCloud's files folder
1375
-	 *
1376
-	 * @param string $userId user ID
1377
-	 * @return \OCP\Files\Folder|null
1378
-	 * @deprecated 20.0.0
1379
-	 */
1380
-	public function getUserFolder($userId = null) {
1381
-		if ($userId === null) {
1382
-			$user = $this->get(IUserSession::class)->getUser();
1383
-			if (!$user) {
1384
-				return null;
1385
-			}
1386
-			$userId = $user->getUID();
1387
-		}
1388
-		$root = $this->get(IRootFolder::class);
1389
-		return $root->getUserFolder($userId);
1390
-	}
1391
-
1392
-	/**
1393
-	 * @return \OC\User\Manager
1394
-	 * @deprecated 20.0.0
1395
-	 */
1396
-	public function getUserManager() {
1397
-		return $this->get(IUserManager::class);
1398
-	}
1399
-
1400
-	/**
1401
-	 * @return \OC\Group\Manager
1402
-	 * @deprecated 20.0.0
1403
-	 */
1404
-	public function getGroupManager() {
1405
-		return $this->get(IGroupManager::class);
1406
-	}
1407
-
1408
-	/**
1409
-	 * @return \OC\User\Session
1410
-	 * @deprecated 20.0.0
1411
-	 */
1412
-	public function getUserSession() {
1413
-		return $this->get(IUserSession::class);
1414
-	}
1415
-
1416
-	/**
1417
-	 * @return \OCP\ISession
1418
-	 * @deprecated 20.0.0
1419
-	 */
1420
-	public function getSession() {
1421
-		return $this->get(Session::class)->getSession();
1422
-	}
1423
-
1424
-	/**
1425
-	 * @param \OCP\ISession $session
1426
-	 * @return void
1427
-	 */
1428
-	public function setSession(\OCP\ISession $session) {
1429
-		$this->get(SessionStorage::class)->setSession($session);
1430
-		$this->get(Session::class)->setSession($session);
1431
-		$this->get(Store::class)->setSession($session);
1432
-	}
1433
-
1434
-	/**
1435
-	 * @return \OCP\IConfig
1436
-	 * @deprecated 20.0.0
1437
-	 */
1438
-	public function getConfig() {
1439
-		return $this->get(AllConfig::class);
1440
-	}
1441
-
1442
-	/**
1443
-	 * @return \OC\SystemConfig
1444
-	 * @deprecated 20.0.0
1445
-	 */
1446
-	public function getSystemConfig() {
1447
-		return $this->get(SystemConfig::class);
1448
-	}
1449
-
1450
-	/**
1451
-	 * @return IFactory
1452
-	 * @deprecated 20.0.0
1453
-	 */
1454
-	public function getL10NFactory() {
1455
-		return $this->get(IFactory::class);
1456
-	}
1457
-
1458
-	/**
1459
-	 * get an L10N instance
1460
-	 *
1461
-	 * @param string $app appid
1462
-	 * @param string $lang
1463
-	 * @return IL10N
1464
-	 * @deprecated 20.0.0 use DI of {@see IL10N} or {@see IFactory} instead, or {@see \OCP\Util::getL10N()} as a last resort
1465
-	 */
1466
-	public function getL10N($app, $lang = null) {
1467
-		return $this->get(IFactory::class)->get($app, $lang);
1468
-	}
1469
-
1470
-	/**
1471
-	 * @return IURLGenerator
1472
-	 * @deprecated 20.0.0
1473
-	 */
1474
-	public function getURLGenerator() {
1475
-		return $this->get(IURLGenerator::class);
1476
-	}
1477
-
1478
-	/**
1479
-	 * Returns an ICache instance. Since 8.1.0 it returns a fake cache. Use
1480
-	 * getMemCacheFactory() instead.
1481
-	 *
1482
-	 * @return ICache
1483
-	 * @deprecated 8.1.0 use getMemCacheFactory to obtain a proper cache
1484
-	 */
1485
-	public function getCache() {
1486
-		return $this->get(ICache::class);
1487
-	}
1488
-
1489
-	/**
1490
-	 * Returns an \OCP\CacheFactory instance
1491
-	 *
1492
-	 * @return \OCP\ICacheFactory
1493
-	 * @deprecated 20.0.0
1494
-	 */
1495
-	public function getMemCacheFactory() {
1496
-		return $this->get(ICacheFactory::class);
1497
-	}
1498
-
1499
-	/**
1500
-	 * Returns the current session
1501
-	 *
1502
-	 * @return \OCP\IDBConnection
1503
-	 * @deprecated 20.0.0
1504
-	 */
1505
-	public function getDatabaseConnection() {
1506
-		return $this->get(IDBConnection::class);
1507
-	}
1508
-
1509
-	/**
1510
-	 * Returns the activity manager
1511
-	 *
1512
-	 * @return \OCP\Activity\IManager
1513
-	 * @deprecated 20.0.0
1514
-	 */
1515
-	public function getActivityManager() {
1516
-		return $this->get(\OCP\Activity\IManager::class);
1517
-	}
1518
-
1519
-	/**
1520
-	 * Returns an job list for controlling background jobs
1521
-	 *
1522
-	 * @return IJobList
1523
-	 * @deprecated 20.0.0
1524
-	 */
1525
-	public function getJobList() {
1526
-		return $this->get(IJobList::class);
1527
-	}
1528
-
1529
-	/**
1530
-	 * Returns a SecureRandom instance
1531
-	 *
1532
-	 * @return \OCP\Security\ISecureRandom
1533
-	 * @deprecated 20.0.0
1534
-	 */
1535
-	public function getSecureRandom() {
1536
-		return $this->get(ISecureRandom::class);
1537
-	}
1538
-
1539
-	/**
1540
-	 * Returns a Crypto instance
1541
-	 *
1542
-	 * @return ICrypto
1543
-	 * @deprecated 20.0.0
1544
-	 */
1545
-	public function getCrypto() {
1546
-		return $this->get(ICrypto::class);
1547
-	}
1548
-
1549
-	/**
1550
-	 * Returns a Hasher instance
1551
-	 *
1552
-	 * @return IHasher
1553
-	 * @deprecated 20.0.0
1554
-	 */
1555
-	public function getHasher() {
1556
-		return $this->get(IHasher::class);
1557
-	}
1558
-
1559
-	/**
1560
-	 * Get the certificate manager
1561
-	 *
1562
-	 * @return \OCP\ICertificateManager
1563
-	 */
1564
-	public function getCertificateManager() {
1565
-		return $this->get(ICertificateManager::class);
1566
-	}
1567
-
1568
-	/**
1569
-	 * Get the manager for temporary files and folders
1570
-	 *
1571
-	 * @return \OCP\ITempManager
1572
-	 * @deprecated 20.0.0
1573
-	 */
1574
-	public function getTempManager() {
1575
-		return $this->get(ITempManager::class);
1576
-	}
1577
-
1578
-	/**
1579
-	 * Get the app manager
1580
-	 *
1581
-	 * @return \OCP\App\IAppManager
1582
-	 * @deprecated 20.0.0
1583
-	 */
1584
-	public function getAppManager() {
1585
-		return $this->get(IAppManager::class);
1586
-	}
1587
-
1588
-	/**
1589
-	 * Creates a new mailer
1590
-	 *
1591
-	 * @return IMailer
1592
-	 * @deprecated 20.0.0
1593
-	 */
1594
-	public function getMailer() {
1595
-		return $this->get(IMailer::class);
1596
-	}
1597
-
1598
-	/**
1599
-	 * Get the webroot
1600
-	 *
1601
-	 * @return string
1602
-	 * @deprecated 20.0.0
1603
-	 */
1604
-	public function getWebRoot() {
1605
-		return $this->webRoot;
1606
-	}
1607
-
1608
-	/**
1609
-	 * Get the locking provider
1610
-	 *
1611
-	 * @return ILockingProvider
1612
-	 * @since 8.1.0
1613
-	 * @deprecated 20.0.0
1614
-	 */
1615
-	public function getLockingProvider() {
1616
-		return $this->get(ILockingProvider::class);
1617
-	}
1618
-
1619
-	/**
1620
-	 * Get the MimeTypeDetector
1621
-	 *
1622
-	 * @return IMimeTypeDetector
1623
-	 * @deprecated 20.0.0
1624
-	 */
1625
-	public function getMimeTypeDetector() {
1626
-		return $this->get(IMimeTypeDetector::class);
1627
-	}
1628
-
1629
-	/**
1630
-	 * Get the MimeTypeLoader
1631
-	 *
1632
-	 * @return IMimeTypeLoader
1633
-	 * @deprecated 20.0.0
1634
-	 */
1635
-	public function getMimeTypeLoader() {
1636
-		return $this->get(IMimeTypeLoader::class);
1637
-	}
1638
-
1639
-	/**
1640
-	 * Get the Notification Manager
1641
-	 *
1642
-	 * @return \OCP\Notification\IManager
1643
-	 * @since 8.2.0
1644
-	 * @deprecated 20.0.0
1645
-	 */
1646
-	public function getNotificationManager() {
1647
-		return $this->get(\OCP\Notification\IManager::class);
1648
-	}
1649
-
1650
-	/**
1651
-	 * @return \OCA\Theming\ThemingDefaults
1652
-	 * @deprecated 20.0.0
1653
-	 */
1654
-	public function getThemingDefaults() {
1655
-		return $this->get('ThemingDefaults');
1656
-	}
1657
-
1658
-	/**
1659
-	 * @return \OC\IntegrityCheck\Checker
1660
-	 * @deprecated 20.0.0
1661
-	 */
1662
-	public function getIntegrityCodeChecker() {
1663
-		return $this->get('IntegrityCodeChecker');
1664
-	}
1665
-
1666
-	/**
1667
-	 * @return CsrfTokenManager
1668
-	 * @deprecated 20.0.0
1669
-	 */
1670
-	public function getCsrfTokenManager() {
1671
-		return $this->get(CsrfTokenManager::class);
1672
-	}
1673
-
1674
-	/**
1675
-	 * @return ContentSecurityPolicyNonceManager
1676
-	 * @deprecated 20.0.0
1677
-	 */
1678
-	public function getContentSecurityPolicyNonceManager() {
1679
-		return $this->get(ContentSecurityPolicyNonceManager::class);
1680
-	}
1681
-
1682
-	/**
1683
-	 * @return \OCP\Settings\IManager
1684
-	 * @deprecated 20.0.0
1685
-	 */
1686
-	public function getSettingsManager() {
1687
-		return $this->get(\OC\Settings\Manager::class);
1688
-	}
1689
-
1690
-	/**
1691
-	 * @return \OCP\Files\IAppData
1692
-	 * @deprecated 20.0.0 Use get(\OCP\Files\AppData\IAppDataFactory::class)->get($app) instead
1693
-	 */
1694
-	public function getAppDataDir($app) {
1695
-		/** @var \OC\Files\AppData\Factory $factory */
1696
-		$factory = $this->get(\OC\Files\AppData\Factory::class);
1697
-		return $factory->get($app);
1698
-	}
1699
-
1700
-	/**
1701
-	 * @return \OCP\Federation\ICloudIdManager
1702
-	 * @deprecated 20.0.0
1703
-	 */
1704
-	public function getCloudIdManager() {
1705
-		return $this->get(ICloudIdManager::class);
1706
-	}
1707
-
1708
-	private function registerDeprecatedAlias(string $alias, string $target) {
1709
-		$this->registerService($alias, function (ContainerInterface $container) use ($target, $alias) {
1710
-			try {
1711
-				/** @var LoggerInterface $logger */
1712
-				$logger = $container->get(LoggerInterface::class);
1713
-				$logger->debug('The requested alias "' . $alias . '" is deprecated. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', ['app' => 'serverDI']);
1714
-			} catch (ContainerExceptionInterface $e) {
1715
-				// Could not get logger. Continue
1716
-			}
1717
-
1718
-			return $container->get($target);
1719
-		}, false);
1720
-	}
1288
+        $this->registerAlias(\OCP\Security\Ip\IFactory::class, \OC\Security\Ip\Factory::class);
1289
+
1290
+        $this->registerAlias(IRichTextFormatter::class, \OC\RichObjectStrings\RichTextFormatter::class);
1291
+
1292
+        $this->registerAlias(ISignatureManager::class, SignatureManager::class);
1293
+
1294
+        $this->connectDispatcher();
1295
+    }
1296
+
1297
+    public function boot() {
1298
+        /** @var HookConnector $hookConnector */
1299
+        $hookConnector = $this->get(HookConnector::class);
1300
+        $hookConnector->viewToNode();
1301
+    }
1302
+
1303
+    private function connectDispatcher(): void {
1304
+        /** @var IEventDispatcher $eventDispatcher */
1305
+        $eventDispatcher = $this->get(IEventDispatcher::class);
1306
+        $eventDispatcher->addServiceListener(LoginFailed::class, LoginFailedListener::class);
1307
+        $eventDispatcher->addServiceListener(PostLoginEvent::class, UserLoggedInListener::class);
1308
+        $eventDispatcher->addServiceListener(UserChangedEvent::class, UserChangedListener::class);
1309
+        $eventDispatcher->addServiceListener(BeforeUserDeletedEvent::class, BeforeUserDeletedListener::class);
1310
+
1311
+        FilesMetadataManager::loadListeners($eventDispatcher);
1312
+        GenerateBlurhashMetadata::loadListeners($eventDispatcher);
1313
+    }
1314
+
1315
+    /**
1316
+     * @return \OCP\Contacts\IManager
1317
+     * @deprecated 20.0.0
1318
+     */
1319
+    public function getContactsManager() {
1320
+        return $this->get(\OCP\Contacts\IManager::class);
1321
+    }
1322
+
1323
+    /**
1324
+     * @return \OC\Encryption\Manager
1325
+     * @deprecated 20.0.0
1326
+     */
1327
+    public function getEncryptionManager() {
1328
+        return $this->get(\OCP\Encryption\IManager::class);
1329
+    }
1330
+
1331
+    /**
1332
+     * @return \OC\Encryption\File
1333
+     * @deprecated 20.0.0
1334
+     */
1335
+    public function getEncryptionFilesHelper() {
1336
+        return $this->get(IFile::class);
1337
+    }
1338
+
1339
+    /**
1340
+     * The current request object holding all information about the request
1341
+     * currently being processed is returned from this method.
1342
+     * In case the current execution was not initiated by a web request null is returned
1343
+     *
1344
+     * @return \OCP\IRequest
1345
+     * @deprecated 20.0.0
1346
+     */
1347
+    public function getRequest() {
1348
+        return $this->get(IRequest::class);
1349
+    }
1350
+
1351
+    /**
1352
+     * Returns the root folder of ownCloud's data directory
1353
+     *
1354
+     * @return IRootFolder
1355
+     * @deprecated 20.0.0
1356
+     */
1357
+    public function getRootFolder() {
1358
+        return $this->get(IRootFolder::class);
1359
+    }
1360
+
1361
+    /**
1362
+     * Returns the root folder of ownCloud's data directory
1363
+     * This is the lazy variant so this gets only initialized once it
1364
+     * is actually used.
1365
+     *
1366
+     * @return IRootFolder
1367
+     * @deprecated 20.0.0
1368
+     */
1369
+    public function getLazyRootFolder() {
1370
+        return $this->get(IRootFolder::class);
1371
+    }
1372
+
1373
+    /**
1374
+     * Returns a view to ownCloud's files folder
1375
+     *
1376
+     * @param string $userId user ID
1377
+     * @return \OCP\Files\Folder|null
1378
+     * @deprecated 20.0.0
1379
+     */
1380
+    public function getUserFolder($userId = null) {
1381
+        if ($userId === null) {
1382
+            $user = $this->get(IUserSession::class)->getUser();
1383
+            if (!$user) {
1384
+                return null;
1385
+            }
1386
+            $userId = $user->getUID();
1387
+        }
1388
+        $root = $this->get(IRootFolder::class);
1389
+        return $root->getUserFolder($userId);
1390
+    }
1391
+
1392
+    /**
1393
+     * @return \OC\User\Manager
1394
+     * @deprecated 20.0.0
1395
+     */
1396
+    public function getUserManager() {
1397
+        return $this->get(IUserManager::class);
1398
+    }
1399
+
1400
+    /**
1401
+     * @return \OC\Group\Manager
1402
+     * @deprecated 20.0.0
1403
+     */
1404
+    public function getGroupManager() {
1405
+        return $this->get(IGroupManager::class);
1406
+    }
1407
+
1408
+    /**
1409
+     * @return \OC\User\Session
1410
+     * @deprecated 20.0.0
1411
+     */
1412
+    public function getUserSession() {
1413
+        return $this->get(IUserSession::class);
1414
+    }
1415
+
1416
+    /**
1417
+     * @return \OCP\ISession
1418
+     * @deprecated 20.0.0
1419
+     */
1420
+    public function getSession() {
1421
+        return $this->get(Session::class)->getSession();
1422
+    }
1423
+
1424
+    /**
1425
+     * @param \OCP\ISession $session
1426
+     * @return void
1427
+     */
1428
+    public function setSession(\OCP\ISession $session) {
1429
+        $this->get(SessionStorage::class)->setSession($session);
1430
+        $this->get(Session::class)->setSession($session);
1431
+        $this->get(Store::class)->setSession($session);
1432
+    }
1433
+
1434
+    /**
1435
+     * @return \OCP\IConfig
1436
+     * @deprecated 20.0.0
1437
+     */
1438
+    public function getConfig() {
1439
+        return $this->get(AllConfig::class);
1440
+    }
1441
+
1442
+    /**
1443
+     * @return \OC\SystemConfig
1444
+     * @deprecated 20.0.0
1445
+     */
1446
+    public function getSystemConfig() {
1447
+        return $this->get(SystemConfig::class);
1448
+    }
1449
+
1450
+    /**
1451
+     * @return IFactory
1452
+     * @deprecated 20.0.0
1453
+     */
1454
+    public function getL10NFactory() {
1455
+        return $this->get(IFactory::class);
1456
+    }
1457
+
1458
+    /**
1459
+     * get an L10N instance
1460
+     *
1461
+     * @param string $app appid
1462
+     * @param string $lang
1463
+     * @return IL10N
1464
+     * @deprecated 20.0.0 use DI of {@see IL10N} or {@see IFactory} instead, or {@see \OCP\Util::getL10N()} as a last resort
1465
+     */
1466
+    public function getL10N($app, $lang = null) {
1467
+        return $this->get(IFactory::class)->get($app, $lang);
1468
+    }
1469
+
1470
+    /**
1471
+     * @return IURLGenerator
1472
+     * @deprecated 20.0.0
1473
+     */
1474
+    public function getURLGenerator() {
1475
+        return $this->get(IURLGenerator::class);
1476
+    }
1477
+
1478
+    /**
1479
+     * Returns an ICache instance. Since 8.1.0 it returns a fake cache. Use
1480
+     * getMemCacheFactory() instead.
1481
+     *
1482
+     * @return ICache
1483
+     * @deprecated 8.1.0 use getMemCacheFactory to obtain a proper cache
1484
+     */
1485
+    public function getCache() {
1486
+        return $this->get(ICache::class);
1487
+    }
1488
+
1489
+    /**
1490
+     * Returns an \OCP\CacheFactory instance
1491
+     *
1492
+     * @return \OCP\ICacheFactory
1493
+     * @deprecated 20.0.0
1494
+     */
1495
+    public function getMemCacheFactory() {
1496
+        return $this->get(ICacheFactory::class);
1497
+    }
1498
+
1499
+    /**
1500
+     * Returns the current session
1501
+     *
1502
+     * @return \OCP\IDBConnection
1503
+     * @deprecated 20.0.0
1504
+     */
1505
+    public function getDatabaseConnection() {
1506
+        return $this->get(IDBConnection::class);
1507
+    }
1508
+
1509
+    /**
1510
+     * Returns the activity manager
1511
+     *
1512
+     * @return \OCP\Activity\IManager
1513
+     * @deprecated 20.0.0
1514
+     */
1515
+    public function getActivityManager() {
1516
+        return $this->get(\OCP\Activity\IManager::class);
1517
+    }
1518
+
1519
+    /**
1520
+     * Returns an job list for controlling background jobs
1521
+     *
1522
+     * @return IJobList
1523
+     * @deprecated 20.0.0
1524
+     */
1525
+    public function getJobList() {
1526
+        return $this->get(IJobList::class);
1527
+    }
1528
+
1529
+    /**
1530
+     * Returns a SecureRandom instance
1531
+     *
1532
+     * @return \OCP\Security\ISecureRandom
1533
+     * @deprecated 20.0.0
1534
+     */
1535
+    public function getSecureRandom() {
1536
+        return $this->get(ISecureRandom::class);
1537
+    }
1538
+
1539
+    /**
1540
+     * Returns a Crypto instance
1541
+     *
1542
+     * @return ICrypto
1543
+     * @deprecated 20.0.0
1544
+     */
1545
+    public function getCrypto() {
1546
+        return $this->get(ICrypto::class);
1547
+    }
1548
+
1549
+    /**
1550
+     * Returns a Hasher instance
1551
+     *
1552
+     * @return IHasher
1553
+     * @deprecated 20.0.0
1554
+     */
1555
+    public function getHasher() {
1556
+        return $this->get(IHasher::class);
1557
+    }
1558
+
1559
+    /**
1560
+     * Get the certificate manager
1561
+     *
1562
+     * @return \OCP\ICertificateManager
1563
+     */
1564
+    public function getCertificateManager() {
1565
+        return $this->get(ICertificateManager::class);
1566
+    }
1567
+
1568
+    /**
1569
+     * Get the manager for temporary files and folders
1570
+     *
1571
+     * @return \OCP\ITempManager
1572
+     * @deprecated 20.0.0
1573
+     */
1574
+    public function getTempManager() {
1575
+        return $this->get(ITempManager::class);
1576
+    }
1577
+
1578
+    /**
1579
+     * Get the app manager
1580
+     *
1581
+     * @return \OCP\App\IAppManager
1582
+     * @deprecated 20.0.0
1583
+     */
1584
+    public function getAppManager() {
1585
+        return $this->get(IAppManager::class);
1586
+    }
1587
+
1588
+    /**
1589
+     * Creates a new mailer
1590
+     *
1591
+     * @return IMailer
1592
+     * @deprecated 20.0.0
1593
+     */
1594
+    public function getMailer() {
1595
+        return $this->get(IMailer::class);
1596
+    }
1597
+
1598
+    /**
1599
+     * Get the webroot
1600
+     *
1601
+     * @return string
1602
+     * @deprecated 20.0.0
1603
+     */
1604
+    public function getWebRoot() {
1605
+        return $this->webRoot;
1606
+    }
1607
+
1608
+    /**
1609
+     * Get the locking provider
1610
+     *
1611
+     * @return ILockingProvider
1612
+     * @since 8.1.0
1613
+     * @deprecated 20.0.0
1614
+     */
1615
+    public function getLockingProvider() {
1616
+        return $this->get(ILockingProvider::class);
1617
+    }
1618
+
1619
+    /**
1620
+     * Get the MimeTypeDetector
1621
+     *
1622
+     * @return IMimeTypeDetector
1623
+     * @deprecated 20.0.0
1624
+     */
1625
+    public function getMimeTypeDetector() {
1626
+        return $this->get(IMimeTypeDetector::class);
1627
+    }
1628
+
1629
+    /**
1630
+     * Get the MimeTypeLoader
1631
+     *
1632
+     * @return IMimeTypeLoader
1633
+     * @deprecated 20.0.0
1634
+     */
1635
+    public function getMimeTypeLoader() {
1636
+        return $this->get(IMimeTypeLoader::class);
1637
+    }
1638
+
1639
+    /**
1640
+     * Get the Notification Manager
1641
+     *
1642
+     * @return \OCP\Notification\IManager
1643
+     * @since 8.2.0
1644
+     * @deprecated 20.0.0
1645
+     */
1646
+    public function getNotificationManager() {
1647
+        return $this->get(\OCP\Notification\IManager::class);
1648
+    }
1649
+
1650
+    /**
1651
+     * @return \OCA\Theming\ThemingDefaults
1652
+     * @deprecated 20.0.0
1653
+     */
1654
+    public function getThemingDefaults() {
1655
+        return $this->get('ThemingDefaults');
1656
+    }
1657
+
1658
+    /**
1659
+     * @return \OC\IntegrityCheck\Checker
1660
+     * @deprecated 20.0.0
1661
+     */
1662
+    public function getIntegrityCodeChecker() {
1663
+        return $this->get('IntegrityCodeChecker');
1664
+    }
1665
+
1666
+    /**
1667
+     * @return CsrfTokenManager
1668
+     * @deprecated 20.0.0
1669
+     */
1670
+    public function getCsrfTokenManager() {
1671
+        return $this->get(CsrfTokenManager::class);
1672
+    }
1673
+
1674
+    /**
1675
+     * @return ContentSecurityPolicyNonceManager
1676
+     * @deprecated 20.0.0
1677
+     */
1678
+    public function getContentSecurityPolicyNonceManager() {
1679
+        return $this->get(ContentSecurityPolicyNonceManager::class);
1680
+    }
1681
+
1682
+    /**
1683
+     * @return \OCP\Settings\IManager
1684
+     * @deprecated 20.0.0
1685
+     */
1686
+    public function getSettingsManager() {
1687
+        return $this->get(\OC\Settings\Manager::class);
1688
+    }
1689
+
1690
+    /**
1691
+     * @return \OCP\Files\IAppData
1692
+     * @deprecated 20.0.0 Use get(\OCP\Files\AppData\IAppDataFactory::class)->get($app) instead
1693
+     */
1694
+    public function getAppDataDir($app) {
1695
+        /** @var \OC\Files\AppData\Factory $factory */
1696
+        $factory = $this->get(\OC\Files\AppData\Factory::class);
1697
+        return $factory->get($app);
1698
+    }
1699
+
1700
+    /**
1701
+     * @return \OCP\Federation\ICloudIdManager
1702
+     * @deprecated 20.0.0
1703
+     */
1704
+    public function getCloudIdManager() {
1705
+        return $this->get(ICloudIdManager::class);
1706
+    }
1707
+
1708
+    private function registerDeprecatedAlias(string $alias, string $target) {
1709
+        $this->registerService($alias, function (ContainerInterface $container) use ($target, $alias) {
1710
+            try {
1711
+                /** @var LoggerInterface $logger */
1712
+                $logger = $container->get(LoggerInterface::class);
1713
+                $logger->debug('The requested alias "' . $alias . '" is deprecated. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', ['app' => 'serverDI']);
1714
+            } catch (ContainerExceptionInterface $e) {
1715
+                // Could not get logger. Continue
1716
+            }
1717
+
1718
+            return $container->get($target);
1719
+        }, false);
1720
+    }
1721 1721
 }
Please login to merge, or discard this patch.
Spacing   +95 added lines, -95 removed lines patch added patch discarded remove patch
@@ -263,10 +263,10 @@  discard block
 block discarded – undo
263 263
 		$this->registerParameter('isCLI', \OC::$CLI);
264 264
 		$this->registerParameter('serverRoot', \OC::$SERVERROOT);
265 265
 
266
-		$this->registerService(ContainerInterface::class, function (ContainerInterface $c) {
266
+		$this->registerService(ContainerInterface::class, function(ContainerInterface $c) {
267 267
 			return $c;
268 268
 		});
269
-		$this->registerService(\OCP\IServerContainer::class, function (ContainerInterface $c) {
269
+		$this->registerService(\OCP\IServerContainer::class, function(ContainerInterface $c) {
270 270
 			return $c;
271 271
 		});
272 272
 
@@ -284,11 +284,11 @@  discard block
 block discarded – undo
284 284
 
285 285
 		$this->registerAlias(IActionFactory::class, ActionFactory::class);
286 286
 
287
-		$this->registerService(View::class, function (Server $c) {
287
+		$this->registerService(View::class, function(Server $c) {
288 288
 			return new View();
289 289
 		}, false);
290 290
 
291
-		$this->registerService(IPreview::class, function (ContainerInterface $c) {
291
+		$this->registerService(IPreview::class, function(ContainerInterface $c) {
292 292
 			return new PreviewManager(
293 293
 				$c->get(\OCP\IConfig::class),
294 294
 				$c->get(IRootFolder::class),
@@ -307,7 +307,7 @@  discard block
 block discarded – undo
307 307
 		});
308 308
 		$this->registerAlias(IMimeIconProvider::class, MimeIconProvider::class);
309 309
 
310
-		$this->registerService(\OC\Preview\Watcher::class, function (ContainerInterface $c) {
310
+		$this->registerService(\OC\Preview\Watcher::class, function(ContainerInterface $c) {
311 311
 			return new \OC\Preview\Watcher(
312 312
 				new \OC\Preview\Storage\Root(
313 313
 					$c->get(IRootFolder::class),
@@ -316,11 +316,11 @@  discard block
 block discarded – undo
316 316
 			);
317 317
 		});
318 318
 
319
-		$this->registerService(IProfiler::class, function (Server $c) {
319
+		$this->registerService(IProfiler::class, function(Server $c) {
320 320
 			return new Profiler($c->get(SystemConfig::class));
321 321
 		});
322 322
 
323
-		$this->registerService(\OCP\Encryption\IManager::class, function (Server $c): Encryption\Manager {
323
+		$this->registerService(\OCP\Encryption\IManager::class, function(Server $c): Encryption\Manager {
324 324
 			$view = new View();
325 325
 			$util = new Encryption\Util(
326 326
 				$view,
@@ -338,7 +338,7 @@  discard block
 block discarded – undo
338 338
 			);
339 339
 		});
340 340
 
341
-		$this->registerService(IFile::class, function (ContainerInterface $c) {
341
+		$this->registerService(IFile::class, function(ContainerInterface $c) {
342 342
 			$util = new Encryption\Util(
343 343
 				new View(),
344 344
 				$c->get(IUserManager::class),
@@ -352,7 +352,7 @@  discard block
 block discarded – undo
352 352
 			);
353 353
 		});
354 354
 
355
-		$this->registerService(IStorage::class, function (ContainerInterface $c) {
355
+		$this->registerService(IStorage::class, function(ContainerInterface $c) {
356 356
 			$view = new View();
357 357
 			$util = new Encryption\Util(
358 358
 				$view,
@@ -371,23 +371,23 @@  discard block
 block discarded – undo
371 371
 
372 372
 		$this->registerAlias(\OCP\ITagManager::class, TagManager::class);
373 373
 
374
-		$this->registerService('SystemTagManagerFactory', function (ContainerInterface $c) {
374
+		$this->registerService('SystemTagManagerFactory', function(ContainerInterface $c) {
375 375
 			/** @var \OCP\IConfig $config */
376 376
 			$config = $c->get(\OCP\IConfig::class);
377 377
 			$factoryClass = $config->getSystemValue('systemtags.managerFactory', SystemTagManagerFactory::class);
378 378
 			return new $factoryClass($this);
379 379
 		});
380
-		$this->registerService(ISystemTagManager::class, function (ContainerInterface $c) {
380
+		$this->registerService(ISystemTagManager::class, function(ContainerInterface $c) {
381 381
 			return $c->get('SystemTagManagerFactory')->getManager();
382 382
 		});
383 383
 		/** @deprecated 19.0.0 */
384 384
 		$this->registerDeprecatedAlias('SystemTagManager', ISystemTagManager::class);
385 385
 
386
-		$this->registerService(ISystemTagObjectMapper::class, function (ContainerInterface $c) {
386
+		$this->registerService(ISystemTagObjectMapper::class, function(ContainerInterface $c) {
387 387
 			return $c->get('SystemTagManagerFactory')->getObjectMapper();
388 388
 		});
389 389
 		$this->registerAlias(IFileAccess::class, FileAccess::class);
390
-		$this->registerService('RootFolder', function (ContainerInterface $c) {
390
+		$this->registerService('RootFolder', function(ContainerInterface $c) {
391 391
 			$manager = \OC\Files\Filesystem::getMountManager();
392 392
 			$view = new View();
393 393
 			/** @var IUserSession $userSession */
@@ -412,7 +412,7 @@  discard block
 block discarded – undo
412 412
 
413 413
 			return $root;
414 414
 		});
415
-		$this->registerService(HookConnector::class, function (ContainerInterface $c) {
415
+		$this->registerService(HookConnector::class, function(ContainerInterface $c) {
416 416
 			return new HookConnector(
417 417
 				$c->get(IRootFolder::class),
418 418
 				new View(),
@@ -421,19 +421,19 @@  discard block
 block discarded – undo
421 421
 			);
422 422
 		});
423 423
 
424
-		$this->registerService(IRootFolder::class, function (ContainerInterface $c) {
425
-			return new LazyRoot(function () use ($c) {
424
+		$this->registerService(IRootFolder::class, function(ContainerInterface $c) {
425
+			return new LazyRoot(function() use ($c) {
426 426
 				return $c->get('RootFolder');
427 427
 			});
428 428
 		});
429 429
 
430 430
 		$this->registerAlias(\OCP\IUserManager::class, \OC\User\Manager::class);
431 431
 
432
-		$this->registerService(DisplayNameCache::class, function (ContainerInterface $c) {
432
+		$this->registerService(DisplayNameCache::class, function(ContainerInterface $c) {
433 433
 			return $c->get(\OC\User\Manager::class)->getDisplayNameCache();
434 434
 		});
435 435
 
436
-		$this->registerService(\OCP\IGroupManager::class, function (ContainerInterface $c) {
436
+		$this->registerService(\OCP\IGroupManager::class, function(ContainerInterface $c) {
437 437
 			$groupManager = new \OC\Group\Manager(
438 438
 				$this->get(IUserManager::class),
439 439
 				$this->get(IEventDispatcher::class),
@@ -444,7 +444,7 @@  discard block
 block discarded – undo
444 444
 			return $groupManager;
445 445
 		});
446 446
 
447
-		$this->registerService(Store::class, function (ContainerInterface $c) {
447
+		$this->registerService(Store::class, function(ContainerInterface $c) {
448 448
 			$session = $c->get(ISession::class);
449 449
 			if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
450 450
 				$tokenProvider = $c->get(IProvider::class);
@@ -459,7 +459,7 @@  discard block
 block discarded – undo
459 459
 		$this->registerAlias(IProvider::class, Authentication\Token\Manager::class);
460 460
 		$this->registerAlias(OCPIProvider::class, Authentication\Token\Manager::class);
461 461
 
462
-		$this->registerService(\OC\User\Session::class, function (Server $c) {
462
+		$this->registerService(\OC\User\Session::class, function(Server $c) {
463 463
 			$manager = $c->get(IUserManager::class);
464 464
 			$session = new \OC\Session\Memory();
465 465
 			$timeFactory = new TimeFactory();
@@ -483,40 +483,40 @@  discard block
 block discarded – undo
483 483
 				$c->get(IEventDispatcher::class),
484 484
 			);
485 485
 			/** @deprecated 21.0.0 use BeforeUserCreatedEvent event with the IEventDispatcher instead */
486
-			$userSession->listen('\OC\User', 'preCreateUser', function ($uid, $password) {
486
+			$userSession->listen('\OC\User', 'preCreateUser', function($uid, $password) {
487 487
 				\OC_Hook::emit('OC_User', 'pre_createUser', ['run' => true, 'uid' => $uid, 'password' => $password]);
488 488
 			});
489 489
 			/** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */
490
-			$userSession->listen('\OC\User', 'postCreateUser', function ($user, $password) {
490
+			$userSession->listen('\OC\User', 'postCreateUser', function($user, $password) {
491 491
 				/** @var \OC\User\User $user */
492 492
 				\OC_Hook::emit('OC_User', 'post_createUser', ['uid' => $user->getUID(), 'password' => $password]);
493 493
 			});
494 494
 			/** @deprecated 21.0.0 use BeforeUserDeletedEvent event with the IEventDispatcher instead */
495
-			$userSession->listen('\OC\User', 'preDelete', function ($user) {
495
+			$userSession->listen('\OC\User', 'preDelete', function($user) {
496 496
 				/** @var \OC\User\User $user */
497 497
 				\OC_Hook::emit('OC_User', 'pre_deleteUser', ['run' => true, 'uid' => $user->getUID()]);
498 498
 			});
499 499
 			/** @deprecated 21.0.0 use UserDeletedEvent event with the IEventDispatcher instead */
500
-			$userSession->listen('\OC\User', 'postDelete', function ($user) {
500
+			$userSession->listen('\OC\User', 'postDelete', function($user) {
501 501
 				/** @var \OC\User\User $user */
502 502
 				\OC_Hook::emit('OC_User', 'post_deleteUser', ['uid' => $user->getUID()]);
503 503
 			});
504
-			$userSession->listen('\OC\User', 'preSetPassword', function ($user, $password, $recoveryPassword) {
504
+			$userSession->listen('\OC\User', 'preSetPassword', function($user, $password, $recoveryPassword) {
505 505
 				/** @var \OC\User\User $user */
506 506
 				\OC_Hook::emit('OC_User', 'pre_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
507 507
 			});
508
-			$userSession->listen('\OC\User', 'postSetPassword', function ($user, $password, $recoveryPassword) {
508
+			$userSession->listen('\OC\User', 'postSetPassword', function($user, $password, $recoveryPassword) {
509 509
 				/** @var \OC\User\User $user */
510 510
 				\OC_Hook::emit('OC_User', 'post_setPassword', ['run' => true, 'uid' => $user->getUID(), 'password' => $password, 'recoveryPassword' => $recoveryPassword]);
511 511
 			});
512
-			$userSession->listen('\OC\User', 'preLogin', function ($uid, $password) {
512
+			$userSession->listen('\OC\User', 'preLogin', function($uid, $password) {
513 513
 				\OC_Hook::emit('OC_User', 'pre_login', ['run' => true, 'uid' => $uid, 'password' => $password]);
514 514
 
515 515
 				/** @var IEventDispatcher $dispatcher */
516 516
 				$dispatcher = $this->get(IEventDispatcher::class);
517 517
 				$dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password));
518 518
 			});
519
-			$userSession->listen('\OC\User', 'postLogin', function ($user, $loginName, $password, $isTokenLogin) {
519
+			$userSession->listen('\OC\User', 'postLogin', function($user, $loginName, $password, $isTokenLogin) {
520 520
 				/** @var \OC\User\User $user */
521 521
 				\OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'loginName' => $loginName, 'password' => $password, 'isTokenLogin' => $isTokenLogin]);
522 522
 
@@ -524,12 +524,12 @@  discard block
 block discarded – undo
524 524
 				$dispatcher = $this->get(IEventDispatcher::class);
525 525
 				$dispatcher->dispatchTyped(new UserLoggedInEvent($user, $loginName, $password, $isTokenLogin));
526 526
 			});
527
-			$userSession->listen('\OC\User', 'preRememberedLogin', function ($uid) {
527
+			$userSession->listen('\OC\User', 'preRememberedLogin', function($uid) {
528 528
 				/** @var IEventDispatcher $dispatcher */
529 529
 				$dispatcher = $this->get(IEventDispatcher::class);
530 530
 				$dispatcher->dispatchTyped(new BeforeUserLoggedInWithCookieEvent($uid));
531 531
 			});
532
-			$userSession->listen('\OC\User', 'postRememberedLogin', function ($user, $password) {
532
+			$userSession->listen('\OC\User', 'postRememberedLogin', function($user, $password) {
533 533
 				/** @var \OC\User\User $user */
534 534
 				\OC_Hook::emit('OC_User', 'post_login', ['run' => true, 'uid' => $user->getUID(), 'password' => $password]);
535 535
 
@@ -537,19 +537,19 @@  discard block
 block discarded – undo
537 537
 				$dispatcher = $this->get(IEventDispatcher::class);
538 538
 				$dispatcher->dispatchTyped(new UserLoggedInWithCookieEvent($user, $password));
539 539
 			});
540
-			$userSession->listen('\OC\User', 'logout', function ($user) {
540
+			$userSession->listen('\OC\User', 'logout', function($user) {
541 541
 				\OC_Hook::emit('OC_User', 'logout', []);
542 542
 
543 543
 				/** @var IEventDispatcher $dispatcher */
544 544
 				$dispatcher = $this->get(IEventDispatcher::class);
545 545
 				$dispatcher->dispatchTyped(new BeforeUserLoggedOutEvent($user));
546 546
 			});
547
-			$userSession->listen('\OC\User', 'postLogout', function ($user) {
547
+			$userSession->listen('\OC\User', 'postLogout', function($user) {
548 548
 				/** @var IEventDispatcher $dispatcher */
549 549
 				$dispatcher = $this->get(IEventDispatcher::class);
550 550
 				$dispatcher->dispatchTyped(new UserLoggedOutEvent($user));
551 551
 			});
552
-			$userSession->listen('\OC\User', 'changeUser', function ($user, $feature, $value, $oldValue) {
552
+			$userSession->listen('\OC\User', 'changeUser', function($user, $feature, $value, $oldValue) {
553 553
 				/** @var \OC\User\User $user */
554 554
 				\OC_Hook::emit('OC_User', 'changeUser', ['run' => true, 'user' => $user, 'feature' => $feature, 'value' => $value, 'old_value' => $oldValue]);
555 555
 			});
@@ -563,14 +563,14 @@  discard block
 block discarded – undo
563 563
 
564 564
 		$this->registerAlias(\OCP\IConfig::class, \OC\AllConfig::class);
565 565
 
566
-		$this->registerService(\OC\SystemConfig::class, function ($c) use ($config) {
566
+		$this->registerService(\OC\SystemConfig::class, function($c) use ($config) {
567 567
 			return new \OC\SystemConfig($config);
568 568
 		});
569 569
 
570 570
 		$this->registerAlias(IAppConfig::class, \OC\AppConfig::class);
571 571
 		$this->registerAlias(IUserConfig::class, \OC\Config\UserConfig::class);
572 572
 
573
-		$this->registerService(IFactory::class, function (Server $c) {
573
+		$this->registerService(IFactory::class, function(Server $c) {
574 574
 			return new \OC\L10N\Factory(
575 575
 				$c->get(\OCP\IConfig::class),
576 576
 				$c->getRequest(),
@@ -583,11 +583,11 @@  discard block
 block discarded – undo
583 583
 
584 584
 		$this->registerAlias(IURLGenerator::class, URLGenerator::class);
585 585
 
586
-		$this->registerService(ICache::class, function ($c) {
586
+		$this->registerService(ICache::class, function($c) {
587 587
 			return new Cache\File();
588 588
 		});
589 589
 
590
-		$this->registerService(Factory::class, function (Server $c) {
590
+		$this->registerService(Factory::class, function(Server $c) {
591 591
 			$profiler = $c->get(IProfiler::class);
592 592
 			$arrayCacheFactory = new \OC\Memcache\Factory(fn () => '', $c->get(LoggerInterface::class),
593 593
 				$profiler,
@@ -602,7 +602,7 @@  discard block
 block discarded – undo
602 602
 
603 603
 			if ($config->getValue('installed', false) && !(defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
604 604
 				$logQuery = $config->getValue('log_query');
605
-				$prefixClosure = function () use ($logQuery, $serverVersion): ?string {
605
+				$prefixClosure = function() use ($logQuery, $serverVersion): ?string {
606 606
 					if (!$logQuery) {
607 607
 						try {
608 608
 							$v = \OCP\Server::get(IAppConfig::class)->getAppInstalledVersions(true);
@@ -620,10 +620,10 @@  discard block
 block discarded – undo
620 620
 						];
621 621
 					}
622 622
 					$v['core'] = implode(',', $serverVersion->getVersion());
623
-					$version = implode(',', array_keys($v)) . implode(',', $v);
623
+					$version = implode(',', array_keys($v)).implode(',', $v);
624 624
 					$instanceId = \OC_Util::getInstanceId();
625 625
 					$path = \OC::$SERVERROOT;
626
-					return md5($instanceId . '-' . $version . '-' . $path);
626
+					return md5($instanceId.'-'.$version.'-'.$path);
627 627
 				};
628 628
 				return new \OC\Memcache\Factory($prefixClosure,
629 629
 					$c->get(LoggerInterface::class),
@@ -642,12 +642,12 @@  discard block
 block discarded – undo
642 642
 		});
643 643
 		$this->registerAlias(ICacheFactory::class, Factory::class);
644 644
 
645
-		$this->registerService('RedisFactory', function (Server $c) {
645
+		$this->registerService('RedisFactory', function(Server $c) {
646 646
 			$systemConfig = $c->get(SystemConfig::class);
647 647
 			return new RedisFactory($systemConfig, $c->get(IEventLogger::class));
648 648
 		});
649 649
 
650
-		$this->registerService(\OCP\Activity\IManager::class, function (Server $c) {
650
+		$this->registerService(\OCP\Activity\IManager::class, function(Server $c) {
651 651
 			$l10n = $this->get(IFactory::class)->get('lib');
652 652
 			return new \OC\Activity\Manager(
653 653
 				$c->getRequest(),
@@ -659,14 +659,14 @@  discard block
 block discarded – undo
659 659
 			);
660 660
 		});
661 661
 
662
-		$this->registerService(\OCP\Activity\IEventMerger::class, function (Server $c) {
662
+		$this->registerService(\OCP\Activity\IEventMerger::class, function(Server $c) {
663 663
 			return new \OC\Activity\EventMerger(
664 664
 				$c->getL10N('lib')
665 665
 			);
666 666
 		});
667 667
 		$this->registerAlias(IValidator::class, Validator::class);
668 668
 
669
-		$this->registerService(AvatarManager::class, function (Server $c) {
669
+		$this->registerService(AvatarManager::class, function(Server $c) {
670 670
 			return new AvatarManager(
671 671
 				$c->get(IUserSession::class),
672 672
 				$c->get(\OC\User\Manager::class),
@@ -686,7 +686,7 @@  discard block
 block discarded – undo
686 686
 		$this->registerAlias(\OCP\Support\Subscription\IAssertion::class, \OC\Support\Subscription\Assertion::class);
687 687
 
688 688
 		/** Only used by the PsrLoggerAdapter should not be used by apps */
689
-		$this->registerService(\OC\Log::class, function (Server $c) {
689
+		$this->registerService(\OC\Log::class, function(Server $c) {
690 690
 			$logType = $c->get(AllConfig::class)->getSystemValue('log_type', 'file');
691 691
 			$factory = new LogFactory($c, $this->get(SystemConfig::class));
692 692
 			$logger = $factory->get($logType);
@@ -697,13 +697,13 @@  discard block
 block discarded – undo
697 697
 		// PSR-3 logger
698 698
 		$this->registerAlias(LoggerInterface::class, PsrLoggerAdapter::class);
699 699
 
700
-		$this->registerService(ILogFactory::class, function (Server $c) {
700
+		$this->registerService(ILogFactory::class, function(Server $c) {
701 701
 			return new LogFactory($c, $this->get(SystemConfig::class));
702 702
 		});
703 703
 
704 704
 		$this->registerAlias(IJobList::class, \OC\BackgroundJob\JobList::class);
705 705
 
706
-		$this->registerService(Router::class, function (Server $c) {
706
+		$this->registerService(Router::class, function(Server $c) {
707 707
 			$cacheFactory = $c->get(ICacheFactory::class);
708 708
 			if ($cacheFactory->isLocalCacheAvailable()) {
709 709
 				$router = $c->resolve(CachingRouter::class);
@@ -714,7 +714,7 @@  discard block
 block discarded – undo
714 714
 		});
715 715
 		$this->registerAlias(IRouter::class, Router::class);
716 716
 
717
-		$this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function ($c) {
717
+		$this->registerService(\OC\Security\RateLimiting\Backend\IBackend::class, function($c) {
718 718
 			$config = $c->get(\OCP\IConfig::class);
719 719
 			if (ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
720 720
 				$backend = new \OC\Security\RateLimiting\Backend\MemoryCacheBackend(
@@ -744,7 +744,7 @@  discard block
 block discarded – undo
744 744
 		$this->registerAlias(ICredentialsManager::class, CredentialsManager::class);
745 745
 
746 746
 		$this->registerAlias(IDBConnection::class, ConnectionAdapter::class);
747
-		$this->registerService(Connection::class, function (Server $c) {
747
+		$this->registerService(Connection::class, function(Server $c) {
748 748
 			$systemConfig = $c->get(SystemConfig::class);
749 749
 			$factory = new \OC\DB\ConnectionFactory($systemConfig, $c->get(ICacheFactory::class));
750 750
 			$type = $systemConfig->getValue('dbtype', 'sqlite');
@@ -757,17 +757,17 @@  discard block
 block discarded – undo
757 757
 
758 758
 		$this->registerAlias(ICertificateManager::class, CertificateManager::class);
759 759
 		$this->registerAlias(IClientService::class, ClientService::class);
760
-		$this->registerService(NegativeDnsCache::class, function (ContainerInterface $c) {
760
+		$this->registerService(NegativeDnsCache::class, function(ContainerInterface $c) {
761 761
 			return new NegativeDnsCache(
762 762
 				$c->get(ICacheFactory::class),
763 763
 			);
764 764
 		});
765 765
 		$this->registerDeprecatedAlias('HttpClientService', IClientService::class);
766
-		$this->registerService(IEventLogger::class, function (ContainerInterface $c) {
766
+		$this->registerService(IEventLogger::class, function(ContainerInterface $c) {
767 767
 			return new EventLogger($c->get(SystemConfig::class), $c->get(LoggerInterface::class), $c->get(Log::class));
768 768
 		});
769 769
 
770
-		$this->registerService(IQueryLogger::class, function (ContainerInterface $c) {
770
+		$this->registerService(IQueryLogger::class, function(ContainerInterface $c) {
771 771
 			$queryLogger = new QueryLogger();
772 772
 			if ($c->get(SystemConfig::class)->getValue('debug', false)) {
773 773
 				// In debug mode, module is being activated by default
@@ -778,7 +778,7 @@  discard block
 block discarded – undo
778 778
 
779 779
 		$this->registerAlias(ITempManager::class, TempManager::class);
780 780
 
781
-		$this->registerService(AppManager::class, function (ContainerInterface $c) {
781
+		$this->registerService(AppManager::class, function(ContainerInterface $c) {
782 782
 			// TODO: use auto-wiring
783 783
 			return new \OC\App\AppManager(
784 784
 				$c->get(IUserSession::class),
@@ -794,7 +794,7 @@  discard block
 block discarded – undo
794 794
 
795 795
 		$this->registerAlias(IDateTimeZone::class, DateTimeZone::class);
796 796
 
797
-		$this->registerService(IDateTimeFormatter::class, function (Server $c) {
797
+		$this->registerService(IDateTimeFormatter::class, function(Server $c) {
798 798
 			$language = $c->get(\OCP\IConfig::class)->getUserValue($c->get(ISession::class)->get('user_id'), 'core', 'lang', null);
799 799
 
800 800
 			return new DateTimeFormatter(
@@ -803,14 +803,14 @@  discard block
 block discarded – undo
803 803
 			);
804 804
 		});
805 805
 
806
-		$this->registerService(IUserMountCache::class, function (ContainerInterface $c) {
806
+		$this->registerService(IUserMountCache::class, function(ContainerInterface $c) {
807 807
 			$mountCache = $c->get(UserMountCache::class);
808 808
 			$listener = new UserMountCacheListener($mountCache);
809 809
 			$listener->listen($c->get(IUserManager::class));
810 810
 			return $mountCache;
811 811
 		});
812 812
 
813
-		$this->registerService(IMountProviderCollection::class, function (ContainerInterface $c) {
813
+		$this->registerService(IMountProviderCollection::class, function(ContainerInterface $c) {
814 814
 			$loader = $c->get(IStorageFactory::class);
815 815
 			$mountCache = $c->get(IUserMountCache::class);
816 816
 			$eventLogger = $c->get(IEventLogger::class);
@@ -830,7 +830,7 @@  discard block
 block discarded – undo
830 830
 			return $manager;
831 831
 		});
832 832
 
833
-		$this->registerService(IBus::class, function (ContainerInterface $c) {
833
+		$this->registerService(IBus::class, function(ContainerInterface $c) {
834 834
 			$busClass = $c->get(\OCP\IConfig::class)->getSystemValueString('commandbus');
835 835
 			if ($busClass) {
836 836
 				[$app, $class] = explode('::', $busClass, 2);
@@ -849,7 +849,7 @@  discard block
 block discarded – undo
849 849
 		$this->registerAlias(ITrustedDomainHelper::class, TrustedDomainHelper::class);
850 850
 		$this->registerAlias(IThrottler::class, Throttler::class);
851 851
 
852
-		$this->registerService(\OC\Security\Bruteforce\Backend\IBackend::class, function ($c) {
852
+		$this->registerService(\OC\Security\Bruteforce\Backend\IBackend::class, function($c) {
853 853
 			$config = $c->get(\OCP\IConfig::class);
854 854
 			if (!$config->getSystemValueBool('auth.bruteforce.protection.force.database', false)
855 855
 				&& ltrim($config->getSystemValueString('memcache.distributed', ''), '\\') === \OC\Memcache\Redis::class) {
@@ -862,7 +862,7 @@  discard block
 block discarded – undo
862 862
 		});
863 863
 
864 864
 		$this->registerDeprecatedAlias('IntegrityCodeChecker', Checker::class);
865
-		$this->registerService(Checker::class, function (ContainerInterface $c) {
865
+		$this->registerService(Checker::class, function(ContainerInterface $c) {
866 866
 			// IConfig requires a working database. This code
867 867
 			// might however be called when Nextcloud is not yet setup.
868 868
 			if (\OC::$server->get(SystemConfig::class)->getValue('installed', false)) {
@@ -885,7 +885,7 @@  discard block
 block discarded – undo
885 885
 				$c->get(IMimeTypeDetector::class)
886 886
 			);
887 887
 		});
888
-		$this->registerService(\OCP\IRequest::class, function (ContainerInterface $c) {
888
+		$this->registerService(\OCP\IRequest::class, function(ContainerInterface $c) {
889 889
 			if (isset($this['urlParams'])) {
890 890
 				$urlParams = $this['urlParams'];
891 891
 			} else {
@@ -920,14 +920,14 @@  discard block
 block discarded – undo
920 920
 			);
921 921
 		});
922 922
 
923
-		$this->registerService(IRequestId::class, function (ContainerInterface $c): IRequestId {
923
+		$this->registerService(IRequestId::class, function(ContainerInterface $c): IRequestId {
924 924
 			return new RequestId(
925 925
 				$_SERVER['UNIQUE_ID'] ?? '',
926 926
 				$this->get(ISecureRandom::class)
927 927
 			);
928 928
 		});
929 929
 
930
-		$this->registerService(IMailer::class, function (Server $c) {
930
+		$this->registerService(IMailer::class, function(Server $c) {
931 931
 			return new Mailer(
932 932
 				$c->get(\OCP\IConfig::class),
933 933
 				$c->get(LoggerInterface::class),
@@ -942,7 +942,7 @@  discard block
 block discarded – undo
942 942
 		/** @since 30.0.0 */
943 943
 		$this->registerAlias(\OCP\Mail\Provider\IManager::class, \OC\Mail\Provider\Manager::class);
944 944
 
945
-		$this->registerService(ILDAPProviderFactory::class, function (ContainerInterface $c) {
945
+		$this->registerService(ILDAPProviderFactory::class, function(ContainerInterface $c) {
946 946
 			$config = $c->get(\OCP\IConfig::class);
947 947
 			$factoryClass = $config->getSystemValue('ldapProviderFactory', null);
948 948
 			if (is_null($factoryClass) || !class_exists($factoryClass)) {
@@ -951,11 +951,11 @@  discard block
 block discarded – undo
951 951
 			/** @var \OCP\LDAP\ILDAPProviderFactory $factory */
952 952
 			return new $factoryClass($this);
953 953
 		});
954
-		$this->registerService(ILDAPProvider::class, function (ContainerInterface $c) {
954
+		$this->registerService(ILDAPProvider::class, function(ContainerInterface $c) {
955 955
 			$factory = $c->get(ILDAPProviderFactory::class);
956 956
 			return $factory->getLDAPProvider();
957 957
 		});
958
-		$this->registerService(ILockingProvider::class, function (ContainerInterface $c) {
958
+		$this->registerService(ILockingProvider::class, function(ContainerInterface $c) {
959 959
 			$ini = $c->get(IniGetWrapper::class);
960 960
 			$config = $c->get(\OCP\IConfig::class);
961 961
 			$ttl = $config->getSystemValueInt('filelocking.ttl', max(3600, $ini->getNumeric('max_execution_time')));
@@ -977,51 +977,51 @@  discard block
 block discarded – undo
977 977
 			return new NoopLockingProvider();
978 978
 		});
979 979
 
980
-		$this->registerService(ILockManager::class, function (Server $c): LockManager {
980
+		$this->registerService(ILockManager::class, function(Server $c): LockManager {
981 981
 			return new LockManager();
982 982
 		});
983 983
 
984 984
 		$this->registerAlias(ILockdownManager::class, 'LockdownManager');
985
-		$this->registerService(SetupManager::class, function ($c) {
985
+		$this->registerService(SetupManager::class, function($c) {
986 986
 			// create the setupmanager through the mount manager to resolve the cyclic dependency
987 987
 			return $c->get(\OC\Files\Mount\Manager::class)->getSetupManager();
988 988
 		});
989 989
 		$this->registerAlias(IMountManager::class, \OC\Files\Mount\Manager::class);
990 990
 
991
-		$this->registerService(IMimeTypeDetector::class, function (ContainerInterface $c) {
991
+		$this->registerService(IMimeTypeDetector::class, function(ContainerInterface $c) {
992 992
 			return new \OC\Files\Type\Detection(
993 993
 				$c->get(IURLGenerator::class),
994 994
 				$c->get(LoggerInterface::class),
995 995
 				\OC::$configDir,
996
-				\OC::$SERVERROOT . '/resources/config/'
996
+				\OC::$SERVERROOT.'/resources/config/'
997 997
 			);
998 998
 		});
999 999
 
1000 1000
 		$this->registerAlias(IMimeTypeLoader::class, Loader::class);
1001
-		$this->registerService(BundleFetcher::class, function () {
1001
+		$this->registerService(BundleFetcher::class, function() {
1002 1002
 			return new BundleFetcher($this->getL10N('lib'));
1003 1003
 		});
1004 1004
 		$this->registerAlias(\OCP\Notification\IManager::class, Manager::class);
1005 1005
 
1006
-		$this->registerService(CapabilitiesManager::class, function (ContainerInterface $c) {
1006
+		$this->registerService(CapabilitiesManager::class, function(ContainerInterface $c) {
1007 1007
 			$manager = new CapabilitiesManager($c->get(LoggerInterface::class));
1008
-			$manager->registerCapability(function () use ($c) {
1008
+			$manager->registerCapability(function() use ($c) {
1009 1009
 				return new \OC\OCS\CoreCapabilities($c->get(\OCP\IConfig::class));
1010 1010
 			});
1011
-			$manager->registerCapability(function () use ($c) {
1011
+			$manager->registerCapability(function() use ($c) {
1012 1012
 				return $c->get(\OC\Security\Bruteforce\Capabilities::class);
1013 1013
 			});
1014 1014
 			return $manager;
1015 1015
 		});
1016 1016
 
1017
-		$this->registerService(ICommentsManager::class, function (Server $c) {
1017
+		$this->registerService(ICommentsManager::class, function(Server $c) {
1018 1018
 			$config = $c->get(\OCP\IConfig::class);
1019 1019
 			$factoryClass = $config->getSystemValue('comments.managerFactory', CommentsManagerFactory::class);
1020 1020
 			/** @var \OCP\Comments\ICommentsManagerFactory $factory */
1021 1021
 			$factory = new $factoryClass($this);
1022 1022
 			$manager = $factory->getManager();
1023 1023
 
1024
-			$manager->registerDisplayNameResolver('user', function ($id) use ($c) {
1024
+			$manager->registerDisplayNameResolver('user', function($id) use ($c) {
1025 1025
 				$manager = $c->get(IUserManager::class);
1026 1026
 				$userDisplayName = $manager->getDisplayName($id);
1027 1027
 				if ($userDisplayName === null) {
@@ -1035,7 +1035,7 @@  discard block
 block discarded – undo
1035 1035
 		});
1036 1036
 
1037 1037
 		$this->registerAlias(\OC_Defaults::class, 'ThemingDefaults');
1038
-		$this->registerService('ThemingDefaults', function (Server $c) {
1038
+		$this->registerService('ThemingDefaults', function(Server $c) {
1039 1039
 			try {
1040 1040
 				$classExists = class_exists('OCA\Theming\ThemingDefaults');
1041 1041
 			} catch (\OCP\AutoloadNotAllowedException $e) {
@@ -1076,7 +1076,7 @@  discard block
 block discarded – undo
1076 1076
 			}
1077 1077
 			return new \OC_Defaults();
1078 1078
 		});
1079
-		$this->registerService(JSCombiner::class, function (Server $c) {
1079
+		$this->registerService(JSCombiner::class, function(Server $c) {
1080 1080
 			return new JSCombiner(
1081 1081
 				$c->getAppDataDir('js'),
1082 1082
 				$c->get(IURLGenerator::class),
@@ -1087,7 +1087,7 @@  discard block
 block discarded – undo
1087 1087
 		});
1088 1088
 		$this->registerAlias(\OCP\EventDispatcher\IEventDispatcher::class, \OC\EventDispatcher\EventDispatcher::class);
1089 1089
 
1090
-		$this->registerService('CryptoWrapper', function (ContainerInterface $c) {
1090
+		$this->registerService('CryptoWrapper', function(ContainerInterface $c) {
1091 1091
 			// FIXME: Instantiated here due to cyclic dependency
1092 1092
 			$request = new Request(
1093 1093
 				[
@@ -1111,12 +1111,12 @@  discard block
 block discarded – undo
1111 1111
 				$request
1112 1112
 			);
1113 1113
 		});
1114
-		$this->registerService(SessionStorage::class, function (ContainerInterface $c) {
1114
+		$this->registerService(SessionStorage::class, function(ContainerInterface $c) {
1115 1115
 			return new SessionStorage($c->get(ISession::class));
1116 1116
 		});
1117 1117
 		$this->registerAlias(\OCP\Security\IContentSecurityPolicyManager::class, ContentSecurityPolicyManager::class);
1118 1118
 
1119
-		$this->registerService(IProviderFactory::class, function (ContainerInterface $c) {
1119
+		$this->registerService(IProviderFactory::class, function(ContainerInterface $c) {
1120 1120
 			$config = $c->get(\OCP\IConfig::class);
1121 1121
 			$factoryClass = $config->getSystemValue('sharing.managerFactory', ProviderFactory::class);
1122 1122
 			/** @var \OCP\Share\IProviderFactory $factory */
@@ -1125,7 +1125,7 @@  discard block
 block discarded – undo
1125 1125
 
1126 1126
 		$this->registerAlias(\OCP\Share\IManager::class, \OC\Share20\Manager::class);
1127 1127
 
1128
-		$this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function (Server $c) {
1128
+		$this->registerService(\OCP\Collaboration\Collaborators\ISearch::class, function(Server $c) {
1129 1129
 			$instance = new Collaboration\Collaborators\Search($c);
1130 1130
 
1131 1131
 			// register default plugins
@@ -1149,20 +1149,20 @@  discard block
 block discarded – undo
1149 1149
 
1150 1150
 		$this->registerDeprecatedAlias('SettingsManager', \OC\Settings\Manager::class);
1151 1151
 		$this->registerAlias(\OCP\Settings\IManager::class, \OC\Settings\Manager::class);
1152
-		$this->registerService(\OC\Files\AppData\Factory::class, function (ContainerInterface $c) {
1152
+		$this->registerService(\OC\Files\AppData\Factory::class, function(ContainerInterface $c) {
1153 1153
 			return new \OC\Files\AppData\Factory(
1154 1154
 				$c->get(IRootFolder::class),
1155 1155
 				$c->get(SystemConfig::class)
1156 1156
 			);
1157 1157
 		});
1158 1158
 
1159
-		$this->registerService('LockdownManager', function (ContainerInterface $c) {
1160
-			return new LockdownManager(function () use ($c) {
1159
+		$this->registerService('LockdownManager', function(ContainerInterface $c) {
1160
+			return new LockdownManager(function() use ($c) {
1161 1161
 				return $c->get(ISession::class);
1162 1162
 			});
1163 1163
 		});
1164 1164
 
1165
-		$this->registerService(\OCP\OCS\IDiscoveryService::class, function (ContainerInterface $c) {
1165
+		$this->registerService(\OCP\OCS\IDiscoveryService::class, function(ContainerInterface $c) {
1166 1166
 			return new DiscoveryService(
1167 1167
 				$c->get(ICacheFactory::class),
1168 1168
 				$c->get(IClientService::class)
@@ -1170,7 +1170,7 @@  discard block
 block discarded – undo
1170 1170
 		});
1171 1171
 		$this->registerAlias(IOCMDiscoveryService::class, OCMDiscoveryService::class);
1172 1172
 
1173
-		$this->registerService(ICloudIdManager::class, function (ContainerInterface $c) {
1173
+		$this->registerService(ICloudIdManager::class, function(ContainerInterface $c) {
1174 1174
 			return new CloudIdManager(
1175 1175
 				$c->get(\OCP\Contacts\IManager::class),
1176 1176
 				$c->get(IURLGenerator::class),
@@ -1182,7 +1182,7 @@  discard block
 block discarded – undo
1182 1182
 
1183 1183
 		$this->registerAlias(\OCP\GlobalScale\IConfig::class, \OC\GlobalScale\Config::class);
1184 1184
 		$this->registerAlias(ICloudFederationProviderManager::class, CloudFederationProviderManager::class);
1185
-		$this->registerService(ICloudFederationFactory::class, function (Server $c) {
1185
+		$this->registerService(ICloudFederationFactory::class, function(Server $c) {
1186 1186
 			return new CloudFederationFactory();
1187 1187
 		});
1188 1188
 
@@ -1191,23 +1191,23 @@  discard block
 block discarded – undo
1191 1191
 		$this->registerAlias(\OCP\AppFramework\Utility\ITimeFactory::class, \OC\AppFramework\Utility\TimeFactory::class);
1192 1192
 		$this->registerAlias(\Psr\Clock\ClockInterface::class, \OCP\AppFramework\Utility\ITimeFactory::class);
1193 1193
 
1194
-		$this->registerService(Defaults::class, function (Server $c) {
1194
+		$this->registerService(Defaults::class, function(Server $c) {
1195 1195
 			return new Defaults(
1196 1196
 				$c->get('ThemingDefaults')
1197 1197
 			);
1198 1198
 		});
1199 1199
 
1200
-		$this->registerService(\OCP\ISession::class, function (ContainerInterface $c) {
1200
+		$this->registerService(\OCP\ISession::class, function(ContainerInterface $c) {
1201 1201
 			return $c->get(\OCP\IUserSession::class)->getSession();
1202 1202
 		}, false);
1203 1203
 
1204
-		$this->registerService(IShareHelper::class, function (ContainerInterface $c) {
1204
+		$this->registerService(IShareHelper::class, function(ContainerInterface $c) {
1205 1205
 			return new ShareHelper(
1206 1206
 				$c->get(\OCP\Share\IManager::class)
1207 1207
 			);
1208 1208
 		});
1209 1209
 
1210
-		$this->registerService(Installer::class, function (ContainerInterface $c) {
1210
+		$this->registerService(Installer::class, function(ContainerInterface $c) {
1211 1211
 			return new Installer(
1212 1212
 				$c->get(AppFetcher::class),
1213 1213
 				$c->get(IClientService::class),
@@ -1218,11 +1218,11 @@  discard block
 block discarded – undo
1218 1218
 			);
1219 1219
 		});
1220 1220
 
1221
-		$this->registerService(IApiFactory::class, function (ContainerInterface $c) {
1221
+		$this->registerService(IApiFactory::class, function(ContainerInterface $c) {
1222 1222
 			return new ApiFactory($c->get(IClientService::class));
1223 1223
 		});
1224 1224
 
1225
-		$this->registerService(IInstanceFactory::class, function (ContainerInterface $c) {
1225
+		$this->registerService(IInstanceFactory::class, function(ContainerInterface $c) {
1226 1226
 			$memcacheFactory = $c->get(ICacheFactory::class);
1227 1227
 			return new InstanceFactory($memcacheFactory->createLocal('remoteinstance.'), $c->get(IClientService::class));
1228 1228
 		});
@@ -1706,11 +1706,11 @@  discard block
 block discarded – undo
1706 1706
 	}
1707 1707
 
1708 1708
 	private function registerDeprecatedAlias(string $alias, string $target) {
1709
-		$this->registerService($alias, function (ContainerInterface $container) use ($target, $alias) {
1709
+		$this->registerService($alias, function(ContainerInterface $container) use ($target, $alias) {
1710 1710
 			try {
1711 1711
 				/** @var LoggerInterface $logger */
1712 1712
 				$logger = $container->get(LoggerInterface::class);
1713
-				$logger->debug('The requested alias "' . $alias . '" is deprecated. Please request "' . $target . '" directly. This alias will be removed in a future Nextcloud version.', ['app' => 'serverDI']);
1713
+				$logger->debug('The requested alias "'.$alias.'" is deprecated. Please request "'.$target.'" directly. This alias will be removed in a future Nextcloud version.', ['app' => 'serverDI']);
1714 1714
 			} catch (ContainerExceptionInterface $e) {
1715 1715
 				// Could not get logger. Continue
1716 1716
 			}
Please login to merge, or discard this patch.
lib/private/TemplateLayout.php 1 patch
Indentation   +369 added lines, -369 removed lines patch added patch discarded remove patch
@@ -35,373 +35,373 @@
 block discarded – undo
35 35
 use OCP\Util;
36 36
 
37 37
 class TemplateLayout {
38
-	private static string $versionHash = '';
39
-	/** @var string[] */
40
-	private static array $cacheBusterCache = [];
41
-
42
-	public static ?CSSResourceLocator $cssLocator = null;
43
-	public static ?JSResourceLocator $jsLocator = null;
44
-
45
-	public function __construct(
46
-		private IConfig $config,
47
-		private IAppManager $appManager,
48
-		private InitialStateService $initialState,
49
-		private INavigationManager $navigationManager,
50
-		private ITemplateManager $templateManager,
51
-		private ServerVersion $serverVersion,
52
-	) {
53
-	}
54
-
55
-	public function getPageTemplate(string $renderAs, string $appId): ITemplate {
56
-		// Add fallback theming variables if not rendered as user
57
-		if ($renderAs !== TemplateResponse::RENDER_AS_USER) {
58
-			// TODO cache generated default theme if enabled for fallback if server is erroring ?
59
-			Util::addStyle('theming', 'default');
60
-		}
61
-
62
-		// Decide which page we show
63
-		switch ($renderAs) {
64
-			case TemplateResponse::RENDER_AS_USER:
65
-				$page = $this->templateManager->getTemplate('core', 'layout.user');
66
-				if (in_array(\OC_App::getCurrentApp(), ['settings','admin', 'help']) !== false) {
67
-					$page->assign('bodyid', 'body-settings');
68
-				} else {
69
-					$page->assign('bodyid', 'body-user');
70
-				}
71
-
72
-				$this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
73
-				$this->initialState->provideInitialState('core', 'apps', array_values($this->navigationManager->getAll()));
74
-
75
-				if ($this->config->getSystemValueBool('unified_search.enabled', false) || !$this->config->getSystemValueBool('enable_non-accessible_features', true)) {
76
-					$this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
77
-					$this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
78
-					$this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
79
-					Util::addScript('core', 'legacy-unified-search', 'core');
80
-				} else {
81
-					Util::addScript('core', 'unified-search', 'core');
82
-				}
83
-
84
-				// Set logo link target
85
-				$logoUrl = $this->config->getSystemValueString('logo_url', '');
86
-				$page->assign('logoUrl', $logoUrl);
87
-
88
-				// Set default entry name
89
-				$defaultEntryId = $this->navigationManager->getDefaultEntryIdForUser();
90
-				$defaultEntry = $this->navigationManager->get($defaultEntryId);
91
-				$page->assign('defaultAppName', $defaultEntry['name'] ?? '');
92
-
93
-				// Add navigation entry
94
-				$page->assign('application', '');
95
-				$page->assign('appid', $appId);
96
-
97
-				$navigation = $this->navigationManager->getAll();
98
-				$page->assign('navigation', $navigation);
99
-				$settingsNavigation = $this->navigationManager->getAll('settings');
100
-				$this->initialState->provideInitialState('core', 'settingsNavEntries', $settingsNavigation);
101
-
102
-				foreach ($navigation as $entry) {
103
-					if ($entry['active']) {
104
-						$page->assign('application', $entry['name']);
105
-						break;
106
-					}
107
-				}
108
-
109
-				foreach ($settingsNavigation as $entry) {
110
-					if ($entry['active']) {
111
-						$page->assign('application', $entry['name']);
112
-						break;
113
-					}
114
-				}
115
-
116
-				$user = Server::get(IUserSession::class)->getUser();
117
-
118
-				if ($user === null) {
119
-					$page->assign('user_uid', false);
120
-					$page->assign('user_displayname', false);
121
-					$page->assign('userAvatarSet', false);
122
-					$page->assign('userStatus', false);
123
-				} else {
124
-					$page->assign('user_uid', $user->getUID());
125
-					$page->assign('user_displayname', $user->getDisplayName());
126
-					$page->assign('userAvatarSet', true);
127
-					$page->assign('userAvatarVersion', $this->config->getUserValue($user->getUID(), 'avatar', 'version', 0));
128
-				}
129
-				break;
130
-			case TemplateResponse::RENDER_AS_ERROR:
131
-				$page = $this->templateManager->getTemplate('core', 'layout.guest', '', false);
132
-				$page->assign('bodyid', 'body-login');
133
-				$page->assign('user_displayname', '');
134
-				$page->assign('user_uid', '');
135
-				break;
136
-			case TemplateResponse::RENDER_AS_GUEST:
137
-				$page = $this->templateManager->getTemplate('core', 'layout.guest');
138
-				Util::addStyle('guest');
139
-				$page->assign('bodyid', 'body-login');
140
-
141
-				$userDisplayName = false;
142
-				$user = Server::get(IUserSession::class)->getUser();
143
-				if ($user) {
144
-					$userDisplayName = $user->getDisplayName();
145
-				}
146
-
147
-				$page->assign('user_displayname', $userDisplayName);
148
-				$page->assign('user_uid', \OC_User::getUser());
149
-				break;
150
-			case TemplateResponse::RENDER_AS_PUBLIC:
151
-				$page = $this->templateManager->getTemplate('core', 'layout.public');
152
-				$page->assign('appid', $appId);
153
-				$page->assign('bodyid', 'body-public');
154
-
155
-				// Set logo link target
156
-				$logoUrl = $this->config->getSystemValueString('logo_url', '');
157
-				$page->assign('logoUrl', $logoUrl);
158
-
159
-				$subscription = Server::get(IRegistry::class);
160
-				$showSimpleSignup = $this->config->getSystemValueBool('simpleSignUpLink.shown', true);
161
-				if ($showSimpleSignup && $subscription->delegateHasValidSubscription()) {
162
-					$showSimpleSignup = false;
163
-				}
164
-
165
-				$defaultSignUpLink = 'https://nextcloud.com/signup/';
166
-				$signUpLink = $this->config->getSystemValueString('registration_link', $defaultSignUpLink);
167
-				if ($signUpLink !== $defaultSignUpLink) {
168
-					$showSimpleSignup = true;
169
-				}
170
-
171
-				if ($this->appManager->isEnabledForUser('registration')) {
172
-					$urlGenerator = Server::get(IURLGenerator::class);
173
-					$signUpLink = $urlGenerator->getAbsoluteURL('/index.php/apps/registration/');
174
-				}
175
-
176
-				$page->assign('showSimpleSignUpLink', $showSimpleSignup);
177
-				$page->assign('signUpLink', $signUpLink);
178
-				break;
179
-			default:
180
-				$page = $this->templateManager->getTemplate('core', 'layout.base');
181
-				break;
182
-		}
183
-		// Send the language, locale, and direction to our layouts
184
-		$l10nFactory = Server::get(IFactory::class);
185
-		$lang = $l10nFactory->findLanguage();
186
-		$locale = $l10nFactory->findLocale($lang);
187
-		$direction = $l10nFactory->getLanguageDirection($lang);
188
-
189
-		$lang = str_replace('_', '-', $lang);
190
-		$page->assign('language', $lang);
191
-		$page->assign('locale', $locale);
192
-		$page->assign('direction', $direction);
193
-
194
-		// Set body data-theme
195
-		try {
196
-			$themesService = Server::get(\OCA\Theming\Service\ThemesService::class);
197
-		} catch (\Exception) {
198
-			$themesService = null;
199
-		}
200
-		$page->assign('enabledThemes', $themesService?->getEnabledThemes() ?? []);
201
-
202
-		if ($this->config->getSystemValueBool('installed', false)) {
203
-			if (empty(self::$versionHash)) {
204
-				$v = $this->appManager->getAppInstalledVersions(true);
205
-				$v['core'] = implode('.', $this->serverVersion->getVersion());
206
-				self::$versionHash = substr(md5(implode(',', $v)), 0, 8);
207
-			}
208
-		} else {
209
-			self::$versionHash = md5('not installed');
210
-		}
211
-
212
-		// Add the js files
213
-		$jsFiles = self::findJavascriptFiles(Util::getScripts());
214
-		$page->assign('jsfiles', []);
215
-		if ($this->config->getSystemValueBool('installed', false) && $renderAs != TemplateResponse::RENDER_AS_ERROR) {
216
-			// this is on purpose outside of the if statement below so that the initial state is prefilled (done in the getConfig() call)
217
-			// see https://github.com/nextcloud/server/pull/22636 for details
218
-			$jsConfigHelper = new JSConfigHelper(
219
-				$this->serverVersion,
220
-				\OCP\Util::getL10N('lib'),
221
-				\OCP\Server::get(Defaults::class),
222
-				$this->appManager,
223
-				\OC::$server->getSession(),
224
-				\OC::$server->getUserSession()->getUser(),
225
-				$this->config,
226
-				\OC::$server->getGroupManager(),
227
-				\OC::$server->get(IniGetWrapper::class),
228
-				\OC::$server->getURLGenerator(),
229
-				\OC::$server->get(CapabilitiesManager::class),
230
-				\OCP\Server::get(IInitialStateService::class),
231
-				\OCP\Server::get(IProvider::class),
232
-				\OCP\Server::get(FilenameValidator::class),
233
-			);
234
-			$config = $jsConfigHelper->getConfig();
235
-			if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) {
236
-				$page->assign('inline_ocjs', $config);
237
-			} else {
238
-				$page->append('jsfiles', \OC::$server->getURLGenerator()->linkToRoute('core.OCJS.getConfig', ['v' => self::$versionHash]));
239
-			}
240
-		}
241
-		foreach ($jsFiles as $info) {
242
-			$web = $info[1];
243
-			$file = $info[2];
244
-			$page->append('jsfiles', $web . '/' . $file . $this->getVersionHashSuffix());
245
-		}
246
-
247
-		$request = \OCP\Server::get(IRequest::class);
248
-
249
-		try {
250
-			$pathInfo = $request->getPathInfo();
251
-		} catch (\Exception $e) {
252
-			$pathInfo = '';
253
-		}
254
-
255
-		// Do not initialise scss appdata until we have a fully installed instance
256
-		// Do not load scss for update, errors, installation or login page
257
-		if ($this->config->getSystemValueBool('installed', false)
258
-			&& !\OCP\Util::needUpgrade()
259
-			&& $pathInfo !== ''
260
-			&& !preg_match('/^\/login/', $pathInfo)
261
-			&& $renderAs !== TemplateResponse::RENDER_AS_ERROR
262
-		) {
263
-			$cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
264
-		} else {
265
-			// If we ignore the scss compiler,
266
-			// we need to load the guest css fallback
267
-			Util::addStyle('guest');
268
-			$cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
269
-		}
270
-
271
-		$page->assign('cssfiles', []);
272
-		$page->assign('printcssfiles', []);
273
-		$this->initialState->provideInitialState('core', 'versionHash', self::$versionHash);
274
-		foreach ($cssFiles as $info) {
275
-			$web = $info[1];
276
-			$file = $info[2];
277
-
278
-			if (str_ends_with($file, 'print.css')) {
279
-				$page->append('printcssfiles', $web . '/' . $file . $this->getVersionHashSuffix());
280
-			} else {
281
-				$suffix = $this->getVersionHashSuffix($web, $file);
282
-
283
-				if (!str_contains($file, '?v=')) {
284
-					$page->append('cssfiles', $web . '/' . $file . $suffix);
285
-				} else {
286
-					$page->append('cssfiles', $web . '/' . $file . '-' . substr($suffix, 3));
287
-				}
288
-			}
289
-		}
290
-
291
-		if ($request->isUserAgent([Request::USER_AGENT_CLIENT_IOS, Request::USER_AGENT_SAFARI, Request::USER_AGENT_SAFARI_MOBILE])) {
292
-			// Prevent auto zoom with iOS but still allow user zoom
293
-			// On chrome (and others) this does not work (will also disable user zoom)
294
-			$page->assign('viewport_maximum_scale', '1.0');
295
-		}
296
-
297
-		$page->assign('initialStates', $this->initialState->getInitialStates());
298
-
299
-		$page->assign('id-app-content', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-content' : '#content');
300
-		$page->assign('id-app-navigation', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-navigation' : null);
301
-
302
-		return $page;
303
-	}
304
-
305
-	protected function getVersionHashSuffix(string $path = '', string $file = ''): string {
306
-		if ($this->config->getSystemValueBool('debug', false)) {
307
-			// allows chrome workspace mapping in debug mode
308
-			return '';
309
-		}
310
-
311
-		if ($this->config->getSystemValueBool('installed', false) === false) {
312
-			// if not installed just return the version hash
313
-			return '?v=' . self::$versionHash;
314
-		}
315
-
316
-		$hash = false;
317
-		// Try the web-root first
318
-		if ($path !== '') {
319
-			$hash = $this->getVersionHashByPath($path);
320
-		}
321
-		// If not found try the file
322
-		if ($hash === false && $file !== '') {
323
-			$hash = $this->getVersionHashByPath($file);
324
-		}
325
-		// As a last resort we use the server version hash
326
-		if ($hash === false) {
327
-			$hash = self::$versionHash;
328
-		}
329
-
330
-		// The theming app is force-enabled thus the cache buster is always available
331
-		$themingSuffix = '-' . $this->config->getAppValue('theming', 'cachebuster', '0');
332
-
333
-		return '?v=' . $hash . $themingSuffix;
334
-	}
335
-
336
-	private function getVersionHashByPath(string $path): string|false {
337
-		if (array_key_exists($path, self::$cacheBusterCache) === false) {
338
-			// Not yet cached, so lets find the cache buster string
339
-			$appId = $this->getAppNamefromPath($path);
340
-			if ($appId === false) {
341
-				// No app Id could be guessed
342
-				return false;
343
-			}
344
-
345
-			if ($appId === 'core') {
346
-				// core is not a real app but the server itself
347
-				$hash = self::$versionHash;
348
-			} else {
349
-				$appVersion = $this->appManager->getAppVersion($appId);
350
-				// For shipped apps the app version is not a single source of truth, we rather also need to consider the Nextcloud version
351
-				if ($this->appManager->isShipped($appId)) {
352
-					$appVersion .= '-' . self::$versionHash;
353
-				}
354
-
355
-				$hash = substr(md5($appVersion), 0, 8);
356
-			}
357
-			self::$cacheBusterCache[$path] = $hash;
358
-		}
359
-
360
-		return self::$cacheBusterCache[$path];
361
-	}
362
-
363
-	public static function findStylesheetFiles(array $styles): array {
364
-		if (!self::$cssLocator) {
365
-			self::$cssLocator = \OCP\Server::get(CSSResourceLocator::class);
366
-		}
367
-		self::$cssLocator->find($styles);
368
-		return self::$cssLocator->getResources();
369
-	}
370
-
371
-	public function getAppNamefromPath(string $path): string|false {
372
-		if ($path !== '') {
373
-			$pathParts = explode('/', $path);
374
-			if ($pathParts[0] === 'css') {
375
-				// This is a scss request
376
-				return $pathParts[1];
377
-			} elseif ($pathParts[0] === 'core') {
378
-				return 'core';
379
-			}
380
-			return end($pathParts);
381
-		}
382
-		return false;
383
-	}
384
-
385
-	public static function findJavascriptFiles(array $scripts): array {
386
-		if (!self::$jsLocator) {
387
-			self::$jsLocator = \OCP\Server::get(JSResourceLocator::class);
388
-		}
389
-		self::$jsLocator->find($scripts);
390
-		return self::$jsLocator->getResources();
391
-	}
392
-
393
-	/**
394
-	 * Converts the absolute file path to a relative path from \OC::$SERVERROOT
395
-	 * @param string $filePath Absolute path
396
-	 * @return string Relative path
397
-	 * @throws \Exception If $filePath is not under \OC::$SERVERROOT
398
-	 */
399
-	public static function convertToRelativePath(string $filePath) {
400
-		$relativePath = explode(\OC::$SERVERROOT, $filePath);
401
-		if (count($relativePath) !== 2) {
402
-			throw new \Exception('$filePath is not under the \OC::$SERVERROOT');
403
-		}
404
-
405
-		return $relativePath[1];
406
-	}
38
+    private static string $versionHash = '';
39
+    /** @var string[] */
40
+    private static array $cacheBusterCache = [];
41
+
42
+    public static ?CSSResourceLocator $cssLocator = null;
43
+    public static ?JSResourceLocator $jsLocator = null;
44
+
45
+    public function __construct(
46
+        private IConfig $config,
47
+        private IAppManager $appManager,
48
+        private InitialStateService $initialState,
49
+        private INavigationManager $navigationManager,
50
+        private ITemplateManager $templateManager,
51
+        private ServerVersion $serverVersion,
52
+    ) {
53
+    }
54
+
55
+    public function getPageTemplate(string $renderAs, string $appId): ITemplate {
56
+        // Add fallback theming variables if not rendered as user
57
+        if ($renderAs !== TemplateResponse::RENDER_AS_USER) {
58
+            // TODO cache generated default theme if enabled for fallback if server is erroring ?
59
+            Util::addStyle('theming', 'default');
60
+        }
61
+
62
+        // Decide which page we show
63
+        switch ($renderAs) {
64
+            case TemplateResponse::RENDER_AS_USER:
65
+                $page = $this->templateManager->getTemplate('core', 'layout.user');
66
+                if (in_array(\OC_App::getCurrentApp(), ['settings','admin', 'help']) !== false) {
67
+                    $page->assign('bodyid', 'body-settings');
68
+                } else {
69
+                    $page->assign('bodyid', 'body-user');
70
+                }
71
+
72
+                $this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
73
+                $this->initialState->provideInitialState('core', 'apps', array_values($this->navigationManager->getAll()));
74
+
75
+                if ($this->config->getSystemValueBool('unified_search.enabled', false) || !$this->config->getSystemValueBool('enable_non-accessible_features', true)) {
76
+                    $this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
77
+                    $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
78
+                    $this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
79
+                    Util::addScript('core', 'legacy-unified-search', 'core');
80
+                } else {
81
+                    Util::addScript('core', 'unified-search', 'core');
82
+                }
83
+
84
+                // Set logo link target
85
+                $logoUrl = $this->config->getSystemValueString('logo_url', '');
86
+                $page->assign('logoUrl', $logoUrl);
87
+
88
+                // Set default entry name
89
+                $defaultEntryId = $this->navigationManager->getDefaultEntryIdForUser();
90
+                $defaultEntry = $this->navigationManager->get($defaultEntryId);
91
+                $page->assign('defaultAppName', $defaultEntry['name'] ?? '');
92
+
93
+                // Add navigation entry
94
+                $page->assign('application', '');
95
+                $page->assign('appid', $appId);
96
+
97
+                $navigation = $this->navigationManager->getAll();
98
+                $page->assign('navigation', $navigation);
99
+                $settingsNavigation = $this->navigationManager->getAll('settings');
100
+                $this->initialState->provideInitialState('core', 'settingsNavEntries', $settingsNavigation);
101
+
102
+                foreach ($navigation as $entry) {
103
+                    if ($entry['active']) {
104
+                        $page->assign('application', $entry['name']);
105
+                        break;
106
+                    }
107
+                }
108
+
109
+                foreach ($settingsNavigation as $entry) {
110
+                    if ($entry['active']) {
111
+                        $page->assign('application', $entry['name']);
112
+                        break;
113
+                    }
114
+                }
115
+
116
+                $user = Server::get(IUserSession::class)->getUser();
117
+
118
+                if ($user === null) {
119
+                    $page->assign('user_uid', false);
120
+                    $page->assign('user_displayname', false);
121
+                    $page->assign('userAvatarSet', false);
122
+                    $page->assign('userStatus', false);
123
+                } else {
124
+                    $page->assign('user_uid', $user->getUID());
125
+                    $page->assign('user_displayname', $user->getDisplayName());
126
+                    $page->assign('userAvatarSet', true);
127
+                    $page->assign('userAvatarVersion', $this->config->getUserValue($user->getUID(), 'avatar', 'version', 0));
128
+                }
129
+                break;
130
+            case TemplateResponse::RENDER_AS_ERROR:
131
+                $page = $this->templateManager->getTemplate('core', 'layout.guest', '', false);
132
+                $page->assign('bodyid', 'body-login');
133
+                $page->assign('user_displayname', '');
134
+                $page->assign('user_uid', '');
135
+                break;
136
+            case TemplateResponse::RENDER_AS_GUEST:
137
+                $page = $this->templateManager->getTemplate('core', 'layout.guest');
138
+                Util::addStyle('guest');
139
+                $page->assign('bodyid', 'body-login');
140
+
141
+                $userDisplayName = false;
142
+                $user = Server::get(IUserSession::class)->getUser();
143
+                if ($user) {
144
+                    $userDisplayName = $user->getDisplayName();
145
+                }
146
+
147
+                $page->assign('user_displayname', $userDisplayName);
148
+                $page->assign('user_uid', \OC_User::getUser());
149
+                break;
150
+            case TemplateResponse::RENDER_AS_PUBLIC:
151
+                $page = $this->templateManager->getTemplate('core', 'layout.public');
152
+                $page->assign('appid', $appId);
153
+                $page->assign('bodyid', 'body-public');
154
+
155
+                // Set logo link target
156
+                $logoUrl = $this->config->getSystemValueString('logo_url', '');
157
+                $page->assign('logoUrl', $logoUrl);
158
+
159
+                $subscription = Server::get(IRegistry::class);
160
+                $showSimpleSignup = $this->config->getSystemValueBool('simpleSignUpLink.shown', true);
161
+                if ($showSimpleSignup && $subscription->delegateHasValidSubscription()) {
162
+                    $showSimpleSignup = false;
163
+                }
164
+
165
+                $defaultSignUpLink = 'https://nextcloud.com/signup/';
166
+                $signUpLink = $this->config->getSystemValueString('registration_link', $defaultSignUpLink);
167
+                if ($signUpLink !== $defaultSignUpLink) {
168
+                    $showSimpleSignup = true;
169
+                }
170
+
171
+                if ($this->appManager->isEnabledForUser('registration')) {
172
+                    $urlGenerator = Server::get(IURLGenerator::class);
173
+                    $signUpLink = $urlGenerator->getAbsoluteURL('/index.php/apps/registration/');
174
+                }
175
+
176
+                $page->assign('showSimpleSignUpLink', $showSimpleSignup);
177
+                $page->assign('signUpLink', $signUpLink);
178
+                break;
179
+            default:
180
+                $page = $this->templateManager->getTemplate('core', 'layout.base');
181
+                break;
182
+        }
183
+        // Send the language, locale, and direction to our layouts
184
+        $l10nFactory = Server::get(IFactory::class);
185
+        $lang = $l10nFactory->findLanguage();
186
+        $locale = $l10nFactory->findLocale($lang);
187
+        $direction = $l10nFactory->getLanguageDirection($lang);
188
+
189
+        $lang = str_replace('_', '-', $lang);
190
+        $page->assign('language', $lang);
191
+        $page->assign('locale', $locale);
192
+        $page->assign('direction', $direction);
193
+
194
+        // Set body data-theme
195
+        try {
196
+            $themesService = Server::get(\OCA\Theming\Service\ThemesService::class);
197
+        } catch (\Exception) {
198
+            $themesService = null;
199
+        }
200
+        $page->assign('enabledThemes', $themesService?->getEnabledThemes() ?? []);
201
+
202
+        if ($this->config->getSystemValueBool('installed', false)) {
203
+            if (empty(self::$versionHash)) {
204
+                $v = $this->appManager->getAppInstalledVersions(true);
205
+                $v['core'] = implode('.', $this->serverVersion->getVersion());
206
+                self::$versionHash = substr(md5(implode(',', $v)), 0, 8);
207
+            }
208
+        } else {
209
+            self::$versionHash = md5('not installed');
210
+        }
211
+
212
+        // Add the js files
213
+        $jsFiles = self::findJavascriptFiles(Util::getScripts());
214
+        $page->assign('jsfiles', []);
215
+        if ($this->config->getSystemValueBool('installed', false) && $renderAs != TemplateResponse::RENDER_AS_ERROR) {
216
+            // this is on purpose outside of the if statement below so that the initial state is prefilled (done in the getConfig() call)
217
+            // see https://github.com/nextcloud/server/pull/22636 for details
218
+            $jsConfigHelper = new JSConfigHelper(
219
+                $this->serverVersion,
220
+                \OCP\Util::getL10N('lib'),
221
+                \OCP\Server::get(Defaults::class),
222
+                $this->appManager,
223
+                \OC::$server->getSession(),
224
+                \OC::$server->getUserSession()->getUser(),
225
+                $this->config,
226
+                \OC::$server->getGroupManager(),
227
+                \OC::$server->get(IniGetWrapper::class),
228
+                \OC::$server->getURLGenerator(),
229
+                \OC::$server->get(CapabilitiesManager::class),
230
+                \OCP\Server::get(IInitialStateService::class),
231
+                \OCP\Server::get(IProvider::class),
232
+                \OCP\Server::get(FilenameValidator::class),
233
+            );
234
+            $config = $jsConfigHelper->getConfig();
235
+            if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) {
236
+                $page->assign('inline_ocjs', $config);
237
+            } else {
238
+                $page->append('jsfiles', \OC::$server->getURLGenerator()->linkToRoute('core.OCJS.getConfig', ['v' => self::$versionHash]));
239
+            }
240
+        }
241
+        foreach ($jsFiles as $info) {
242
+            $web = $info[1];
243
+            $file = $info[2];
244
+            $page->append('jsfiles', $web . '/' . $file . $this->getVersionHashSuffix());
245
+        }
246
+
247
+        $request = \OCP\Server::get(IRequest::class);
248
+
249
+        try {
250
+            $pathInfo = $request->getPathInfo();
251
+        } catch (\Exception $e) {
252
+            $pathInfo = '';
253
+        }
254
+
255
+        // Do not initialise scss appdata until we have a fully installed instance
256
+        // Do not load scss for update, errors, installation or login page
257
+        if ($this->config->getSystemValueBool('installed', false)
258
+            && !\OCP\Util::needUpgrade()
259
+            && $pathInfo !== ''
260
+            && !preg_match('/^\/login/', $pathInfo)
261
+            && $renderAs !== TemplateResponse::RENDER_AS_ERROR
262
+        ) {
263
+            $cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
264
+        } else {
265
+            // If we ignore the scss compiler,
266
+            // we need to load the guest css fallback
267
+            Util::addStyle('guest');
268
+            $cssFiles = self::findStylesheetFiles(\OC_Util::$styles);
269
+        }
270
+
271
+        $page->assign('cssfiles', []);
272
+        $page->assign('printcssfiles', []);
273
+        $this->initialState->provideInitialState('core', 'versionHash', self::$versionHash);
274
+        foreach ($cssFiles as $info) {
275
+            $web = $info[1];
276
+            $file = $info[2];
277
+
278
+            if (str_ends_with($file, 'print.css')) {
279
+                $page->append('printcssfiles', $web . '/' . $file . $this->getVersionHashSuffix());
280
+            } else {
281
+                $suffix = $this->getVersionHashSuffix($web, $file);
282
+
283
+                if (!str_contains($file, '?v=')) {
284
+                    $page->append('cssfiles', $web . '/' . $file . $suffix);
285
+                } else {
286
+                    $page->append('cssfiles', $web . '/' . $file . '-' . substr($suffix, 3));
287
+                }
288
+            }
289
+        }
290
+
291
+        if ($request->isUserAgent([Request::USER_AGENT_CLIENT_IOS, Request::USER_AGENT_SAFARI, Request::USER_AGENT_SAFARI_MOBILE])) {
292
+            // Prevent auto zoom with iOS but still allow user zoom
293
+            // On chrome (and others) this does not work (will also disable user zoom)
294
+            $page->assign('viewport_maximum_scale', '1.0');
295
+        }
296
+
297
+        $page->assign('initialStates', $this->initialState->getInitialStates());
298
+
299
+        $page->assign('id-app-content', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-content' : '#content');
300
+        $page->assign('id-app-navigation', $renderAs === TemplateResponse::RENDER_AS_USER ? '#app-navigation' : null);
301
+
302
+        return $page;
303
+    }
304
+
305
+    protected function getVersionHashSuffix(string $path = '', string $file = ''): string {
306
+        if ($this->config->getSystemValueBool('debug', false)) {
307
+            // allows chrome workspace mapping in debug mode
308
+            return '';
309
+        }
310
+
311
+        if ($this->config->getSystemValueBool('installed', false) === false) {
312
+            // if not installed just return the version hash
313
+            return '?v=' . self::$versionHash;
314
+        }
315
+
316
+        $hash = false;
317
+        // Try the web-root first
318
+        if ($path !== '') {
319
+            $hash = $this->getVersionHashByPath($path);
320
+        }
321
+        // If not found try the file
322
+        if ($hash === false && $file !== '') {
323
+            $hash = $this->getVersionHashByPath($file);
324
+        }
325
+        // As a last resort we use the server version hash
326
+        if ($hash === false) {
327
+            $hash = self::$versionHash;
328
+        }
329
+
330
+        // The theming app is force-enabled thus the cache buster is always available
331
+        $themingSuffix = '-' . $this->config->getAppValue('theming', 'cachebuster', '0');
332
+
333
+        return '?v=' . $hash . $themingSuffix;
334
+    }
335
+
336
+    private function getVersionHashByPath(string $path): string|false {
337
+        if (array_key_exists($path, self::$cacheBusterCache) === false) {
338
+            // Not yet cached, so lets find the cache buster string
339
+            $appId = $this->getAppNamefromPath($path);
340
+            if ($appId === false) {
341
+                // No app Id could be guessed
342
+                return false;
343
+            }
344
+
345
+            if ($appId === 'core') {
346
+                // core is not a real app but the server itself
347
+                $hash = self::$versionHash;
348
+            } else {
349
+                $appVersion = $this->appManager->getAppVersion($appId);
350
+                // For shipped apps the app version is not a single source of truth, we rather also need to consider the Nextcloud version
351
+                if ($this->appManager->isShipped($appId)) {
352
+                    $appVersion .= '-' . self::$versionHash;
353
+                }
354
+
355
+                $hash = substr(md5($appVersion), 0, 8);
356
+            }
357
+            self::$cacheBusterCache[$path] = $hash;
358
+        }
359
+
360
+        return self::$cacheBusterCache[$path];
361
+    }
362
+
363
+    public static function findStylesheetFiles(array $styles): array {
364
+        if (!self::$cssLocator) {
365
+            self::$cssLocator = \OCP\Server::get(CSSResourceLocator::class);
366
+        }
367
+        self::$cssLocator->find($styles);
368
+        return self::$cssLocator->getResources();
369
+    }
370
+
371
+    public function getAppNamefromPath(string $path): string|false {
372
+        if ($path !== '') {
373
+            $pathParts = explode('/', $path);
374
+            if ($pathParts[0] === 'css') {
375
+                // This is a scss request
376
+                return $pathParts[1];
377
+            } elseif ($pathParts[0] === 'core') {
378
+                return 'core';
379
+            }
380
+            return end($pathParts);
381
+        }
382
+        return false;
383
+    }
384
+
385
+    public static function findJavascriptFiles(array $scripts): array {
386
+        if (!self::$jsLocator) {
387
+            self::$jsLocator = \OCP\Server::get(JSResourceLocator::class);
388
+        }
389
+        self::$jsLocator->find($scripts);
390
+        return self::$jsLocator->getResources();
391
+    }
392
+
393
+    /**
394
+     * Converts the absolute file path to a relative path from \OC::$SERVERROOT
395
+     * @param string $filePath Absolute path
396
+     * @return string Relative path
397
+     * @throws \Exception If $filePath is not under \OC::$SERVERROOT
398
+     */
399
+    public static function convertToRelativePath(string $filePath) {
400
+        $relativePath = explode(\OC::$SERVERROOT, $filePath);
401
+        if (count($relativePath) !== 2) {
402
+            throw new \Exception('$filePath is not under the \OC::$SERVERROOT');
403
+        }
404
+
405
+        return $relativePath[1];
406
+    }
407 407
 }
Please login to merge, or discard this patch.
lib/private/App/AppManager.php 1 patch
Indentation   +909 added lines, -909 removed lines patch added patch discarded remove patch
@@ -32,913 +32,913 @@
 block discarded – undo
32 32
 use Psr\Log\LoggerInterface;
33 33
 
34 34
 class AppManager implements IAppManager {
35
-	/**
36
-	 * Apps with these types can not be enabled for certain groups only
37
-	 * @var string[]
38
-	 */
39
-	protected $protectedAppTypes = [
40
-		'filesystem',
41
-		'prelogin',
42
-		'authentication',
43
-		'logging',
44
-		'prevent_group_restriction',
45
-	];
46
-
47
-	/** @var string[] $appId => $enabled */
48
-	private array $enabledAppsCache = [];
49
-
50
-	/** @var string[]|null */
51
-	private ?array $shippedApps = null;
52
-
53
-	private array $alwaysEnabled = [];
54
-	private array $defaultEnabled = [];
55
-
56
-	/** @var array */
57
-	private array $appInfos = [];
58
-
59
-	/** @var array */
60
-	private array $appVersions = [];
61
-
62
-	/** @var array */
63
-	private array $autoDisabledApps = [];
64
-	private array $appTypes = [];
65
-
66
-	/** @var array<string, true> */
67
-	private array $loadedApps = [];
68
-
69
-	private ?AppConfig $appConfig = null;
70
-	private ?IURLGenerator $urlGenerator = null;
71
-	private ?INavigationManager $navigationManager = null;
72
-
73
-	/**
74
-	 * Be extremely careful when injecting classes here. The AppManager is used by the installer,
75
-	 * so it needs to work before installation. See how AppConfig and IURLGenerator are injected for reference
76
-	 */
77
-	public function __construct(
78
-		private IUserSession $userSession,
79
-		private IConfig $config,
80
-		private IGroupManager $groupManager,
81
-		private ICacheFactory $memCacheFactory,
82
-		private IEventDispatcher $dispatcher,
83
-		private LoggerInterface $logger,
84
-		private ServerVersion $serverVersion,
85
-	) {
86
-	}
87
-
88
-	private function getNavigationManager(): INavigationManager {
89
-		if ($this->navigationManager === null) {
90
-			$this->navigationManager = \OCP\Server::get(INavigationManager::class);
91
-		}
92
-		return $this->navigationManager;
93
-	}
94
-
95
-	public function getAppIcon(string $appId, bool $dark = false): ?string {
96
-		$possibleIcons = $dark ? [$appId . '-dark.svg', 'app-dark.svg'] : [$appId . '.svg', 'app.svg'];
97
-		$icon = null;
98
-		foreach ($possibleIcons as $iconName) {
99
-			try {
100
-				$icon = $this->getUrlGenerator()->imagePath($appId, $iconName);
101
-				break;
102
-			} catch (\RuntimeException $e) {
103
-				// ignore
104
-			}
105
-		}
106
-		return $icon;
107
-	}
108
-
109
-	private function getAppConfig(): AppConfig {
110
-		if ($this->appConfig !== null) {
111
-			return $this->appConfig;
112
-		}
113
-		if (!$this->config->getSystemValueBool('installed', false)) {
114
-			throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
115
-		}
116
-		$this->appConfig = \OCP\Server::get(AppConfig::class);
117
-		return $this->appConfig;
118
-	}
119
-
120
-	private function getUrlGenerator(): IURLGenerator {
121
-		if ($this->urlGenerator !== null) {
122
-			return $this->urlGenerator;
123
-		}
124
-		if (!$this->config->getSystemValueBool('installed', false)) {
125
-			throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
126
-		}
127
-		$this->urlGenerator = \OCP\Server::get(IURLGenerator::class);
128
-		return $this->urlGenerator;
129
-	}
130
-
131
-	/**
132
-	 * For all enabled apps, return the value of their 'enabled' config key.
133
-	 *
134
-	 * @return array<string,string> appId => enabled (may be 'yes', or a json encoded list of group ids)
135
-	 */
136
-	private function getEnabledAppsValues(): array {
137
-		if (!$this->enabledAppsCache) {
138
-			/** @var array<string,string> */
139
-			$values = $this->getAppConfig()->searchValues('enabled', false, IAppConfig::VALUE_STRING);
140
-
141
-			$alwaysEnabledApps = $this->getAlwaysEnabledApps();
142
-			foreach ($alwaysEnabledApps as $appId) {
143
-				$values[$appId] = 'yes';
144
-			}
145
-
146
-			$this->enabledAppsCache = array_filter($values, function ($value) {
147
-				return $value !== 'no';
148
-			});
149
-			ksort($this->enabledAppsCache);
150
-		}
151
-		return $this->enabledAppsCache;
152
-	}
153
-
154
-	/**
155
-	 * Deprecated alias
156
-	 *
157
-	 * @return string[]
158
-	 */
159
-	public function getInstalledApps() {
160
-		return $this->getEnabledApps();
161
-	}
162
-
163
-	/**
164
-	 * List all enabled apps, either for everyone or for some groups
165
-	 *
166
-	 * @return list<string>
167
-	 */
168
-	public function getEnabledApps(): array {
169
-		return array_keys($this->getEnabledAppsValues());
170
-	}
171
-
172
-	/**
173
-	 * Get a list of all apps in the apps folder
174
-	 *
175
-	 * @return list<string> an array of app names (string IDs)
176
-	 */
177
-	public function getAllAppsInAppsFolders(): array {
178
-		$apps = [];
179
-
180
-		foreach (\OC::$APPSROOTS as $apps_dir) {
181
-			if (!is_readable($apps_dir['path'])) {
182
-				$this->logger->warning('unable to read app folder : ' . $apps_dir['path'], ['app' => 'core']);
183
-				continue;
184
-			}
185
-			$dh = opendir($apps_dir['path']);
186
-
187
-			if (is_resource($dh)) {
188
-				while (($file = readdir($dh)) !== false) {
189
-					if (
190
-						$file[0] != '.' &&
191
-						is_dir($apps_dir['path'] . '/' . $file) &&
192
-						is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')
193
-					) {
194
-						$apps[] = $file;
195
-					}
196
-				}
197
-			}
198
-		}
199
-
200
-		return array_values(array_unique($apps));
201
-	}
202
-
203
-	/**
204
-	 * List all apps enabled for a user
205
-	 *
206
-	 * @param \OCP\IUser $user
207
-	 * @return string[]
208
-	 */
209
-	public function getEnabledAppsForUser(IUser $user) {
210
-		$apps = $this->getEnabledAppsValues();
211
-		$appsForUser = array_filter($apps, function ($enabled) use ($user) {
212
-			return $this->checkAppForUser($enabled, $user);
213
-		});
214
-		return array_keys($appsForUser);
215
-	}
216
-
217
-	public function getEnabledAppsForGroup(IGroup $group): array {
218
-		$apps = $this->getEnabledAppsValues();
219
-		$appsForGroups = array_filter($apps, function ($enabled) use ($group) {
220
-			return $this->checkAppForGroups($enabled, $group);
221
-		});
222
-		return array_keys($appsForGroups);
223
-	}
224
-
225
-	/**
226
-	 * Loads all apps
227
-	 *
228
-	 * @param string[] $types
229
-	 * @return bool
230
-	 *
231
-	 * This function walks through the Nextcloud directory and loads all apps
232
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
233
-	 * exists.
234
-	 *
235
-	 * if $types is set to non-empty array, only apps of those types will be loaded
236
-	 */
237
-	public function loadApps(array $types = []): bool {
238
-		if ($this->config->getSystemValueBool('maintenance', false)) {
239
-			return false;
240
-		}
241
-		// Load the enabled apps here
242
-		$apps = \OC_App::getEnabledApps();
243
-
244
-		// Add each apps' folder as allowed class path
245
-		foreach ($apps as $app) {
246
-			// If the app is already loaded then autoloading it makes no sense
247
-			if (!$this->isAppLoaded($app)) {
248
-				$path = \OC_App::getAppPath($app);
249
-				if ($path !== false) {
250
-					\OC_App::registerAutoloading($app, $path);
251
-				}
252
-			}
253
-		}
254
-
255
-		// prevent app loading from printing output
256
-		ob_start();
257
-		foreach ($apps as $app) {
258
-			if (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))) {
259
-				try {
260
-					$this->loadApp($app);
261
-				} catch (\Throwable $e) {
262
-					$this->logger->emergency('Error during app loading: ' . $e->getMessage(), [
263
-						'exception' => $e,
264
-						'app' => $app,
265
-					]);
266
-				}
267
-			}
268
-		}
269
-		ob_end_clean();
270
-
271
-		return true;
272
-	}
273
-
274
-	/**
275
-	 * check if an app is of a specific type
276
-	 *
277
-	 * @param string $app
278
-	 * @param array $types
279
-	 * @return bool
280
-	 */
281
-	public function isType(string $app, array $types): bool {
282
-		$appTypes = $this->getAppTypes($app);
283
-		foreach ($types as $type) {
284
-			if (in_array($type, $appTypes, true)) {
285
-				return true;
286
-			}
287
-		}
288
-		return false;
289
-	}
290
-
291
-	/**
292
-	 * get the types of an app
293
-	 *
294
-	 * @param string $app
295
-	 * @return string[]
296
-	 */
297
-	private function getAppTypes(string $app): array {
298
-		//load the cache
299
-		if (count($this->appTypes) === 0) {
300
-			$this->appTypes = $this->getAppConfig()->getValues(false, 'types') ?: [];
301
-		}
302
-
303
-		if (isset($this->appTypes[$app])) {
304
-			return explode(',', $this->appTypes[$app]);
305
-		}
306
-
307
-		return [];
308
-	}
309
-
310
-	/**
311
-	 * @return array
312
-	 */
313
-	public function getAutoDisabledApps(): array {
314
-		return $this->autoDisabledApps;
315
-	}
316
-
317
-	public function getAppRestriction(string $appId): array {
318
-		$values = $this->getEnabledAppsValues();
319
-
320
-		if (!isset($values[$appId])) {
321
-			return [];
322
-		}
323
-
324
-		if ($values[$appId] === 'yes' || $values[$appId] === 'no') {
325
-			return [];
326
-		}
327
-		return json_decode($values[$appId], true);
328
-	}
329
-
330
-	/**
331
-	 * Check if an app is enabled for user
332
-	 *
333
-	 * @param string $appId
334
-	 * @param \OCP\IUser|null $user (optional) if not defined, the currently logged in user will be used
335
-	 * @return bool
336
-	 */
337
-	public function isEnabledForUser($appId, $user = null) {
338
-		if ($this->isAlwaysEnabled($appId)) {
339
-			return true;
340
-		}
341
-		if ($user === null) {
342
-			$user = $this->userSession->getUser();
343
-		}
344
-		$enabledAppsValues = $this->getEnabledAppsValues();
345
-		if (isset($enabledAppsValues[$appId])) {
346
-			return $this->checkAppForUser($enabledAppsValues[$appId], $user);
347
-		} else {
348
-			return false;
349
-		}
350
-	}
351
-
352
-	private function checkAppForUser(string $enabled, ?IUser $user): bool {
353
-		if ($enabled === 'yes') {
354
-			return true;
355
-		} elseif ($user === null) {
356
-			return false;
357
-		} else {
358
-			if (empty($enabled)) {
359
-				return false;
360
-			}
361
-
362
-			$groupIds = json_decode($enabled);
363
-
364
-			if (!is_array($groupIds)) {
365
-				$jsonError = json_last_error();
366
-				$jsonErrorMsg = json_last_error_msg();
367
-				// this really should never happen (if it does, the admin should check the `enabled` key value via `occ config:list` because it's bogus for some reason)
368
-				$this->logger->warning('AppManager::checkAppForUser - can\'t decode group IDs listed in app\'s enabled config key: ' . print_r($enabled, true) . ' - JSON error (' . $jsonError . ') ' . $jsonErrorMsg);
369
-				return false;
370
-			}
371
-
372
-			$userGroups = $this->groupManager->getUserGroupIds($user);
373
-			foreach ($userGroups as $groupId) {
374
-				if (in_array($groupId, $groupIds, true)) {
375
-					return true;
376
-				}
377
-			}
378
-			return false;
379
-		}
380
-	}
381
-
382
-	private function checkAppForGroups(string $enabled, IGroup $group): bool {
383
-		if ($enabled === 'yes') {
384
-			return true;
385
-		} else {
386
-			if (empty($enabled)) {
387
-				return false;
388
-			}
389
-
390
-			$groupIds = json_decode($enabled);
391
-
392
-			if (!is_array($groupIds)) {
393
-				$jsonError = json_last_error();
394
-				$jsonErrorMsg = json_last_error_msg();
395
-				// this really should never happen (if it does, the admin should check the `enabled` key value via `occ config:list` because it's bogus for some reason)
396
-				$this->logger->warning('AppManager::checkAppForGroups - can\'t decode group IDs listed in app\'s enabled config key: ' . print_r($enabled, true) . ' - JSON error (' . $jsonError . ') ' . $jsonErrorMsg);
397
-				return false;
398
-			}
399
-
400
-			return in_array($group->getGID(), $groupIds);
401
-		}
402
-	}
403
-
404
-	/**
405
-	 * Check if an app is enabled in the instance
406
-	 *
407
-	 * Notice: This actually checks if the app is enabled and not only if it is installed.
408
-	 *
409
-	 * @param string $appId
410
-	 */
411
-	public function isInstalled($appId): bool {
412
-		return $this->isEnabledForAnyone($appId);
413
-	}
414
-
415
-	public function isEnabledForAnyone(string $appId): bool {
416
-		$enabledAppsValues = $this->getEnabledAppsValues();
417
-		return isset($enabledAppsValues[$appId]);
418
-	}
419
-
420
-	/**
421
-	 * Overwrite the `max-version` requirement for this app.
422
-	 */
423
-	public function overwriteNextcloudRequirement(string $appId): void {
424
-		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
425
-		if (!in_array($appId, $ignoreMaxApps, true)) {
426
-			$ignoreMaxApps[] = $appId;
427
-		}
428
-		$this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
429
-	}
430
-
431
-	/**
432
-	 * Remove the `max-version` overwrite for this app.
433
-	 * This means this app now again can not be enabled if the `max-version` is smaller than the current Nextcloud version.
434
-	 */
435
-	public function removeOverwriteNextcloudRequirement(string $appId): void {
436
-		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
437
-		$ignoreMaxApps = array_filter($ignoreMaxApps, fn (string $id) => $id !== $appId);
438
-		$this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
439
-	}
440
-
441
-	public function loadApp(string $app): void {
442
-		if (isset($this->loadedApps[$app])) {
443
-			return;
444
-		}
445
-		$this->loadedApps[$app] = true;
446
-		$appPath = \OC_App::getAppPath($app);
447
-		if ($appPath === false) {
448
-			return;
449
-		}
450
-		$eventLogger = \OC::$server->get(IEventLogger::class);
451
-		$eventLogger->start("bootstrap:load_app:$app", "Load app: $app");
452
-
453
-		// in case someone calls loadApp() directly
454
-		\OC_App::registerAutoloading($app, $appPath);
455
-
456
-		if (is_file($appPath . '/appinfo/app.php')) {
457
-			$this->logger->error('/appinfo/app.php is not supported anymore, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
458
-				'app' => $app,
459
-			]);
460
-		}
461
-
462
-		$coordinator = \OCP\Server::get(Coordinator::class);
463
-		$coordinator->bootApp($app);
464
-
465
-		$eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it");
466
-		$info = $this->getAppInfo($app);
467
-		if (!empty($info['activity'])) {
468
-			$activityManager = \OC::$server->get(IActivityManager::class);
469
-			if (!empty($info['activity']['filters'])) {
470
-				foreach ($info['activity']['filters'] as $filter) {
471
-					$activityManager->registerFilter($filter);
472
-				}
473
-			}
474
-			if (!empty($info['activity']['settings'])) {
475
-				foreach ($info['activity']['settings'] as $setting) {
476
-					$activityManager->registerSetting($setting);
477
-				}
478
-			}
479
-			if (!empty($info['activity']['providers'])) {
480
-				foreach ($info['activity']['providers'] as $provider) {
481
-					$activityManager->registerProvider($provider);
482
-				}
483
-			}
484
-		}
485
-
486
-		if (!empty($info['settings'])) {
487
-			$settingsManager = \OC::$server->get(ISettingsManager::class);
488
-			if (!empty($info['settings']['admin'])) {
489
-				foreach ($info['settings']['admin'] as $setting) {
490
-					$settingsManager->registerSetting('admin', $setting);
491
-				}
492
-			}
493
-			if (!empty($info['settings']['admin-section'])) {
494
-				foreach ($info['settings']['admin-section'] as $section) {
495
-					$settingsManager->registerSection('admin', $section);
496
-				}
497
-			}
498
-			if (!empty($info['settings']['personal'])) {
499
-				foreach ($info['settings']['personal'] as $setting) {
500
-					$settingsManager->registerSetting('personal', $setting);
501
-				}
502
-			}
503
-			if (!empty($info['settings']['personal-section'])) {
504
-				foreach ($info['settings']['personal-section'] as $section) {
505
-					$settingsManager->registerSection('personal', $section);
506
-				}
507
-			}
508
-		}
509
-
510
-		if (!empty($info['collaboration']['plugins'])) {
511
-			// deal with one or many plugin entries
512
-			$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
513
-				[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
514
-			$collaboratorSearch = null;
515
-			$autoCompleteManager = null;
516
-			foreach ($plugins as $plugin) {
517
-				if ($plugin['@attributes']['type'] === 'collaborator-search') {
518
-					$pluginInfo = [
519
-						'shareType' => $plugin['@attributes']['share-type'],
520
-						'class' => $plugin['@value'],
521
-					];
522
-					$collaboratorSearch ??= \OC::$server->get(ICollaboratorSearch::class);
523
-					$collaboratorSearch->registerPlugin($pluginInfo);
524
-				} elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
525
-					$autoCompleteManager ??= \OC::$server->get(IAutoCompleteManager::class);
526
-					$autoCompleteManager->registerSorter($plugin['@value']);
527
-				}
528
-			}
529
-		}
530
-		$eventLogger->end("bootstrap:load_app:$app:info");
531
-
532
-		$eventLogger->end("bootstrap:load_app:$app");
533
-	}
534
-
535
-	/**
536
-	 * Check if an app is loaded
537
-	 * @param string $app app id
538
-	 * @since 26.0.0
539
-	 */
540
-	public function isAppLoaded(string $app): bool {
541
-		return isset($this->loadedApps[$app]);
542
-	}
543
-
544
-	/**
545
-	 * Enable an app for every user
546
-	 *
547
-	 * @param string $appId
548
-	 * @param bool $forceEnable
549
-	 * @throws AppPathNotFoundException
550
-	 * @throws \InvalidArgumentException if the application is not installed yet
551
-	 */
552
-	public function enableApp(string $appId, bool $forceEnable = false): void {
553
-		// Check if app exists
554
-		$this->getAppPath($appId);
555
-
556
-		if ($this->config->getAppValue($appId, 'installed_version', '') === '') {
557
-			throw new \InvalidArgumentException("$appId is not installed, cannot be enabled.");
558
-		}
559
-
560
-		if ($forceEnable) {
561
-			$this->overwriteNextcloudRequirement($appId);
562
-		}
563
-
564
-		$this->enabledAppsCache[$appId] = 'yes';
565
-		$this->getAppConfig()->setValue($appId, 'enabled', 'yes');
566
-		$this->dispatcher->dispatchTyped(new AppEnableEvent($appId));
567
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
568
-			ManagerEvent::EVENT_APP_ENABLE, $appId
569
-		));
570
-		$this->clearAppsCache();
571
-	}
572
-
573
-	/**
574
-	 * Whether a list of types contains a protected app type
575
-	 *
576
-	 * @param string[] $types
577
-	 * @return bool
578
-	 */
579
-	public function hasProtectedAppType($types) {
580
-		if (empty($types)) {
581
-			return false;
582
-		}
583
-
584
-		$protectedTypes = array_intersect($this->protectedAppTypes, $types);
585
-		return !empty($protectedTypes);
586
-	}
587
-
588
-	/**
589
-	 * Enable an app only for specific groups
590
-	 *
591
-	 * @param string $appId
592
-	 * @param IGroup[] $groups
593
-	 * @param bool $forceEnable
594
-	 * @throws \InvalidArgumentException if app can't be enabled for groups
595
-	 * @throws AppPathNotFoundException
596
-	 */
597
-	public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void {
598
-		// Check if app exists
599
-		$this->getAppPath($appId);
600
-
601
-		$info = $this->getAppInfo($appId);
602
-		if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) {
603
-			throw new \InvalidArgumentException("$appId can't be enabled for groups.");
604
-		}
605
-
606
-		if ($this->config->getAppValue($appId, 'installed_version', '') === '') {
607
-			throw new \InvalidArgumentException("$appId is not installed, cannot be enabled.");
608
-		}
609
-
610
-		if ($forceEnable) {
611
-			$this->overwriteNextcloudRequirement($appId);
612
-		}
613
-
614
-		/** @var string[] $groupIds */
615
-		$groupIds = array_map(function ($group) {
616
-			/** @var IGroup $group */
617
-			return ($group instanceof IGroup)
618
-				? $group->getGID()
619
-				: $group;
620
-		}, $groups);
621
-
622
-		$this->enabledAppsCache[$appId] = json_encode($groupIds);
623
-		$this->getAppConfig()->setValue($appId, 'enabled', json_encode($groupIds));
624
-		$this->dispatcher->dispatchTyped(new AppEnableEvent($appId, $groupIds));
625
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
626
-			ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
627
-		));
628
-		$this->clearAppsCache();
629
-	}
630
-
631
-	/**
632
-	 * Disable an app for every user
633
-	 *
634
-	 * @param string $appId
635
-	 * @param bool $automaticDisabled
636
-	 * @throws \Exception if app can't be disabled
637
-	 */
638
-	public function disableApp($appId, $automaticDisabled = false): void {
639
-		if ($this->isAlwaysEnabled($appId)) {
640
-			throw new \Exception("$appId can't be disabled.");
641
-		}
642
-
643
-		if ($automaticDisabled) {
644
-			$previousSetting = $this->getAppConfig()->getValue($appId, 'enabled', 'yes');
645
-			if ($previousSetting !== 'yes' && $previousSetting !== 'no') {
646
-				$previousSetting = json_decode($previousSetting, true);
647
-			}
648
-			$this->autoDisabledApps[$appId] = $previousSetting;
649
-		}
650
-
651
-		unset($this->enabledAppsCache[$appId]);
652
-		$this->getAppConfig()->setValue($appId, 'enabled', 'no');
653
-
654
-		// run uninstall steps
655
-		$appData = $this->getAppInfo($appId);
656
-		if (!is_null($appData)) {
657
-			\OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
658
-		}
659
-
660
-		$this->dispatcher->dispatchTyped(new AppDisableEvent($appId));
661
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
662
-			ManagerEvent::EVENT_APP_DISABLE, $appId
663
-		));
664
-		$this->clearAppsCache();
665
-	}
666
-
667
-	/**
668
-	 * Get the directory for the given app.
669
-	 *
670
-	 * @throws AppPathNotFoundException if app folder can't be found
671
-	 */
672
-	public function getAppPath(string $appId): string {
673
-		$appPath = \OC_App::getAppPath($appId);
674
-		if ($appPath === false) {
675
-			throw new AppPathNotFoundException('Could not find path for ' . $appId);
676
-		}
677
-		return $appPath;
678
-	}
679
-
680
-	/**
681
-	 * Get the web path for the given app.
682
-	 *
683
-	 * @param string $appId
684
-	 * @return string
685
-	 * @throws AppPathNotFoundException if app path can't be found
686
-	 */
687
-	public function getAppWebPath(string $appId): string {
688
-		$appWebPath = \OC_App::getAppWebPath($appId);
689
-		if ($appWebPath === false) {
690
-			throw new AppPathNotFoundException('Could not find web path for ' . $appId);
691
-		}
692
-		return $appWebPath;
693
-	}
694
-
695
-	/**
696
-	 * Clear the cached list of apps when enabling/disabling an app
697
-	 */
698
-	public function clearAppsCache(): void {
699
-		$this->appInfos = [];
700
-	}
701
-
702
-	/**
703
-	 * Returns a list of apps that need upgrade
704
-	 *
705
-	 * @param string $version Nextcloud version as array of version components
706
-	 * @return array list of app info from apps that need an upgrade
707
-	 *
708
-	 * @internal
709
-	 */
710
-	public function getAppsNeedingUpgrade($version) {
711
-		$appsToUpgrade = [];
712
-		$apps = $this->getEnabledApps();
713
-		foreach ($apps as $appId) {
714
-			$appInfo = $this->getAppInfo($appId);
715
-			$appDbVersion = $this->getAppConfig()->getValue($appId, 'installed_version');
716
-			if ($appDbVersion
717
-				&& isset($appInfo['version'])
718
-				&& version_compare($appInfo['version'], $appDbVersion, '>')
719
-				&& \OC_App::isAppCompatible($version, $appInfo)
720
-			) {
721
-				$appsToUpgrade[] = $appInfo;
722
-			}
723
-		}
724
-
725
-		return $appsToUpgrade;
726
-	}
727
-
728
-	/**
729
-	 * Returns the app information from "appinfo/info.xml".
730
-	 *
731
-	 * @param string|null $lang
732
-	 * @return array|null app info
733
-	 */
734
-	public function getAppInfo(string $appId, bool $path = false, $lang = null) {
735
-		if ($path) {
736
-			throw new \InvalidArgumentException('Calling IAppManager::getAppInfo() with a path is no longer supported. Please call IAppManager::getAppInfoByPath() instead and verify that the path is good before calling.');
737
-		}
738
-		if ($lang === null && isset($this->appInfos[$appId])) {
739
-			return $this->appInfos[$appId];
740
-		}
741
-		try {
742
-			$appPath = $this->getAppPath($appId);
743
-		} catch (AppPathNotFoundException) {
744
-			return null;
745
-		}
746
-		$file = $appPath . '/appinfo/info.xml';
747
-
748
-		$data = $this->getAppInfoByPath($file, $lang);
749
-
750
-		if ($lang === null) {
751
-			$this->appInfos[$appId] = $data;
752
-		}
753
-
754
-		return $data;
755
-	}
756
-
757
-	public function getAppInfoByPath(string $path, ?string $lang = null): ?array {
758
-		if (!str_ends_with($path, '/appinfo/info.xml')) {
759
-			return null;
760
-		}
761
-
762
-		$parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
763
-		$data = $parser->parse($path);
764
-
765
-		if (is_array($data)) {
766
-			$data = \OC_App::parseAppInfo($data, $lang);
767
-		}
768
-
769
-		return $data;
770
-	}
771
-
772
-	public function getAppVersion(string $appId, bool $useCache = true): string {
773
-		if (!$useCache || !isset($this->appVersions[$appId])) {
774
-			if ($appId === 'core') {
775
-				$this->appVersions[$appId] = $this->serverVersion->getVersionString();
776
-			} else {
777
-				$appInfo = $this->getAppInfo($appId);
778
-				$this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
779
-			}
780
-		}
781
-		return $this->appVersions[$appId];
782
-	}
783
-
784
-	/**
785
-	 * Returns the installed versions of all apps
786
-	 *
787
-	 * @return array<string, string>
788
-	 */
789
-	public function getAppInstalledVersions(bool $onlyEnabled = false): array {
790
-		return $this->getAppConfig()->getAppInstalledVersions($onlyEnabled);
791
-	}
792
-
793
-	/**
794
-	 * Returns a list of apps incompatible with the given version
795
-	 *
796
-	 * @param string $version Nextcloud version as array of version components
797
-	 *
798
-	 * @return array list of app info from incompatible apps
799
-	 *
800
-	 * @internal
801
-	 */
802
-	public function getIncompatibleApps(string $version): array {
803
-		$apps = $this->getEnabledApps();
804
-		$incompatibleApps = [];
805
-		foreach ($apps as $appId) {
806
-			$info = $this->getAppInfo($appId);
807
-			if ($info === null) {
808
-				$incompatibleApps[] = ['id' => $appId, 'name' => $appId];
809
-			} elseif (!\OC_App::isAppCompatible($version, $info)) {
810
-				$incompatibleApps[] = $info;
811
-			}
812
-		}
813
-		return $incompatibleApps;
814
-	}
815
-
816
-	/**
817
-	 * @inheritdoc
818
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
819
-	 */
820
-	public function isShipped($appId) {
821
-		$this->loadShippedJson();
822
-		return in_array($appId, $this->shippedApps, true);
823
-	}
824
-
825
-	private function isAlwaysEnabled(string $appId): bool {
826
-		if ($appId === 'core') {
827
-			return true;
828
-		}
829
-
830
-		$alwaysEnabled = $this->getAlwaysEnabledApps();
831
-		return in_array($appId, $alwaysEnabled, true);
832
-	}
833
-
834
-	/**
835
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
836
-	 * @throws \Exception
837
-	 */
838
-	private function loadShippedJson(): void {
839
-		if ($this->shippedApps === null) {
840
-			$shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
841
-			if (!file_exists($shippedJson)) {
842
-				throw new \Exception("File not found: $shippedJson");
843
-			}
844
-			$content = json_decode(file_get_contents($shippedJson), true);
845
-			$this->shippedApps = $content['shippedApps'];
846
-			$this->alwaysEnabled = $content['alwaysEnabled'];
847
-			$this->defaultEnabled = $content['defaultEnabled'];
848
-		}
849
-	}
850
-
851
-	/**
852
-	 * @inheritdoc
853
-	 */
854
-	public function getAlwaysEnabledApps() {
855
-		$this->loadShippedJson();
856
-		return $this->alwaysEnabled;
857
-	}
858
-
859
-	/**
860
-	 * @inheritdoc
861
-	 */
862
-	public function isDefaultEnabled(string $appId): bool {
863
-		return (in_array($appId, $this->getDefaultEnabledApps()));
864
-	}
865
-
866
-	/**
867
-	 * @inheritdoc
868
-	 */
869
-	public function getDefaultEnabledApps(): array {
870
-		$this->loadShippedJson();
871
-
872
-		return $this->defaultEnabled;
873
-	}
874
-
875
-	/**
876
-	 * @inheritdoc
877
-	 */
878
-	public function getDefaultAppForUser(?IUser $user = null, bool $withFallbacks = true): string {
879
-		$id = $this->getNavigationManager()->getDefaultEntryIdForUser($user, $withFallbacks);
880
-		$entry = $this->getNavigationManager()->get($id);
881
-		return (string)$entry['app'];
882
-	}
883
-
884
-	/**
885
-	 * @inheritdoc
886
-	 */
887
-	public function getDefaultApps(): array {
888
-		$ids = $this->getNavigationManager()->getDefaultEntryIds();
889
-
890
-		return array_values(array_unique(array_map(function (string $id) {
891
-			$entry = $this->getNavigationManager()->get($id);
892
-			return (string)$entry['app'];
893
-		}, $ids)));
894
-	}
895
-
896
-	/**
897
-	 * @inheritdoc
898
-	 */
899
-	public function setDefaultApps(array $defaultApps): void {
900
-		$entries = $this->getNavigationManager()->getAll();
901
-		$ids = [];
902
-		foreach ($defaultApps as $defaultApp) {
903
-			foreach ($entries as $entry) {
904
-				if ((string)$entry['app'] === $defaultApp) {
905
-					$ids[] = (string)$entry['id'];
906
-					break;
907
-				}
908
-			}
909
-		}
910
-		$this->getNavigationManager()->setDefaultEntryIds($ids);
911
-	}
912
-
913
-	public function isBackendRequired(string $backend): bool {
914
-		foreach ($this->appInfos as $appInfo) {
915
-			foreach ($appInfo['dependencies']['backend'] as $appBackend) {
916
-				if ($backend === $appBackend) {
917
-					return true;
918
-				}
919
-			}
920
-		}
921
-
922
-		return false;
923
-	}
924
-
925
-	/**
926
-	 * Clean the appId from forbidden characters
927
-	 *
928
-	 * @psalm-taint-escape callable
929
-	 * @psalm-taint-escape cookie
930
-	 * @psalm-taint-escape file
931
-	 * @psalm-taint-escape has_quotes
932
-	 * @psalm-taint-escape header
933
-	 * @psalm-taint-escape html
934
-	 * @psalm-taint-escape include
935
-	 * @psalm-taint-escape ldap
936
-	 * @psalm-taint-escape shell
937
-	 * @psalm-taint-escape sql
938
-	 * @psalm-taint-escape unserialize
939
-	 */
940
-	public function cleanAppId(string $app): string {
941
-		/* Only lowercase alphanumeric is allowed */
942
-		return preg_replace('/(^[0-9_]|[^a-z0-9_]+|_$)/', '', $app);
943
-	}
35
+    /**
36
+     * Apps with these types can not be enabled for certain groups only
37
+     * @var string[]
38
+     */
39
+    protected $protectedAppTypes = [
40
+        'filesystem',
41
+        'prelogin',
42
+        'authentication',
43
+        'logging',
44
+        'prevent_group_restriction',
45
+    ];
46
+
47
+    /** @var string[] $appId => $enabled */
48
+    private array $enabledAppsCache = [];
49
+
50
+    /** @var string[]|null */
51
+    private ?array $shippedApps = null;
52
+
53
+    private array $alwaysEnabled = [];
54
+    private array $defaultEnabled = [];
55
+
56
+    /** @var array */
57
+    private array $appInfos = [];
58
+
59
+    /** @var array */
60
+    private array $appVersions = [];
61
+
62
+    /** @var array */
63
+    private array $autoDisabledApps = [];
64
+    private array $appTypes = [];
65
+
66
+    /** @var array<string, true> */
67
+    private array $loadedApps = [];
68
+
69
+    private ?AppConfig $appConfig = null;
70
+    private ?IURLGenerator $urlGenerator = null;
71
+    private ?INavigationManager $navigationManager = null;
72
+
73
+    /**
74
+     * Be extremely careful when injecting classes here. The AppManager is used by the installer,
75
+     * so it needs to work before installation. See how AppConfig and IURLGenerator are injected for reference
76
+     */
77
+    public function __construct(
78
+        private IUserSession $userSession,
79
+        private IConfig $config,
80
+        private IGroupManager $groupManager,
81
+        private ICacheFactory $memCacheFactory,
82
+        private IEventDispatcher $dispatcher,
83
+        private LoggerInterface $logger,
84
+        private ServerVersion $serverVersion,
85
+    ) {
86
+    }
87
+
88
+    private function getNavigationManager(): INavigationManager {
89
+        if ($this->navigationManager === null) {
90
+            $this->navigationManager = \OCP\Server::get(INavigationManager::class);
91
+        }
92
+        return $this->navigationManager;
93
+    }
94
+
95
+    public function getAppIcon(string $appId, bool $dark = false): ?string {
96
+        $possibleIcons = $dark ? [$appId . '-dark.svg', 'app-dark.svg'] : [$appId . '.svg', 'app.svg'];
97
+        $icon = null;
98
+        foreach ($possibleIcons as $iconName) {
99
+            try {
100
+                $icon = $this->getUrlGenerator()->imagePath($appId, $iconName);
101
+                break;
102
+            } catch (\RuntimeException $e) {
103
+                // ignore
104
+            }
105
+        }
106
+        return $icon;
107
+    }
108
+
109
+    private function getAppConfig(): AppConfig {
110
+        if ($this->appConfig !== null) {
111
+            return $this->appConfig;
112
+        }
113
+        if (!$this->config->getSystemValueBool('installed', false)) {
114
+            throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
115
+        }
116
+        $this->appConfig = \OCP\Server::get(AppConfig::class);
117
+        return $this->appConfig;
118
+    }
119
+
120
+    private function getUrlGenerator(): IURLGenerator {
121
+        if ($this->urlGenerator !== null) {
122
+            return $this->urlGenerator;
123
+        }
124
+        if (!$this->config->getSystemValueBool('installed', false)) {
125
+            throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
126
+        }
127
+        $this->urlGenerator = \OCP\Server::get(IURLGenerator::class);
128
+        return $this->urlGenerator;
129
+    }
130
+
131
+    /**
132
+     * For all enabled apps, return the value of their 'enabled' config key.
133
+     *
134
+     * @return array<string,string> appId => enabled (may be 'yes', or a json encoded list of group ids)
135
+     */
136
+    private function getEnabledAppsValues(): array {
137
+        if (!$this->enabledAppsCache) {
138
+            /** @var array<string,string> */
139
+            $values = $this->getAppConfig()->searchValues('enabled', false, IAppConfig::VALUE_STRING);
140
+
141
+            $alwaysEnabledApps = $this->getAlwaysEnabledApps();
142
+            foreach ($alwaysEnabledApps as $appId) {
143
+                $values[$appId] = 'yes';
144
+            }
145
+
146
+            $this->enabledAppsCache = array_filter($values, function ($value) {
147
+                return $value !== 'no';
148
+            });
149
+            ksort($this->enabledAppsCache);
150
+        }
151
+        return $this->enabledAppsCache;
152
+    }
153
+
154
+    /**
155
+     * Deprecated alias
156
+     *
157
+     * @return string[]
158
+     */
159
+    public function getInstalledApps() {
160
+        return $this->getEnabledApps();
161
+    }
162
+
163
+    /**
164
+     * List all enabled apps, either for everyone or for some groups
165
+     *
166
+     * @return list<string>
167
+     */
168
+    public function getEnabledApps(): array {
169
+        return array_keys($this->getEnabledAppsValues());
170
+    }
171
+
172
+    /**
173
+     * Get a list of all apps in the apps folder
174
+     *
175
+     * @return list<string> an array of app names (string IDs)
176
+     */
177
+    public function getAllAppsInAppsFolders(): array {
178
+        $apps = [];
179
+
180
+        foreach (\OC::$APPSROOTS as $apps_dir) {
181
+            if (!is_readable($apps_dir['path'])) {
182
+                $this->logger->warning('unable to read app folder : ' . $apps_dir['path'], ['app' => 'core']);
183
+                continue;
184
+            }
185
+            $dh = opendir($apps_dir['path']);
186
+
187
+            if (is_resource($dh)) {
188
+                while (($file = readdir($dh)) !== false) {
189
+                    if (
190
+                        $file[0] != '.' &&
191
+                        is_dir($apps_dir['path'] . '/' . $file) &&
192
+                        is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')
193
+                    ) {
194
+                        $apps[] = $file;
195
+                    }
196
+                }
197
+            }
198
+        }
199
+
200
+        return array_values(array_unique($apps));
201
+    }
202
+
203
+    /**
204
+     * List all apps enabled for a user
205
+     *
206
+     * @param \OCP\IUser $user
207
+     * @return string[]
208
+     */
209
+    public function getEnabledAppsForUser(IUser $user) {
210
+        $apps = $this->getEnabledAppsValues();
211
+        $appsForUser = array_filter($apps, function ($enabled) use ($user) {
212
+            return $this->checkAppForUser($enabled, $user);
213
+        });
214
+        return array_keys($appsForUser);
215
+    }
216
+
217
+    public function getEnabledAppsForGroup(IGroup $group): array {
218
+        $apps = $this->getEnabledAppsValues();
219
+        $appsForGroups = array_filter($apps, function ($enabled) use ($group) {
220
+            return $this->checkAppForGroups($enabled, $group);
221
+        });
222
+        return array_keys($appsForGroups);
223
+    }
224
+
225
+    /**
226
+     * Loads all apps
227
+     *
228
+     * @param string[] $types
229
+     * @return bool
230
+     *
231
+     * This function walks through the Nextcloud directory and loads all apps
232
+     * it can find. A directory contains an app if the file /appinfo/info.xml
233
+     * exists.
234
+     *
235
+     * if $types is set to non-empty array, only apps of those types will be loaded
236
+     */
237
+    public function loadApps(array $types = []): bool {
238
+        if ($this->config->getSystemValueBool('maintenance', false)) {
239
+            return false;
240
+        }
241
+        // Load the enabled apps here
242
+        $apps = \OC_App::getEnabledApps();
243
+
244
+        // Add each apps' folder as allowed class path
245
+        foreach ($apps as $app) {
246
+            // If the app is already loaded then autoloading it makes no sense
247
+            if (!$this->isAppLoaded($app)) {
248
+                $path = \OC_App::getAppPath($app);
249
+                if ($path !== false) {
250
+                    \OC_App::registerAutoloading($app, $path);
251
+                }
252
+            }
253
+        }
254
+
255
+        // prevent app loading from printing output
256
+        ob_start();
257
+        foreach ($apps as $app) {
258
+            if (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))) {
259
+                try {
260
+                    $this->loadApp($app);
261
+                } catch (\Throwable $e) {
262
+                    $this->logger->emergency('Error during app loading: ' . $e->getMessage(), [
263
+                        'exception' => $e,
264
+                        'app' => $app,
265
+                    ]);
266
+                }
267
+            }
268
+        }
269
+        ob_end_clean();
270
+
271
+        return true;
272
+    }
273
+
274
+    /**
275
+     * check if an app is of a specific type
276
+     *
277
+     * @param string $app
278
+     * @param array $types
279
+     * @return bool
280
+     */
281
+    public function isType(string $app, array $types): bool {
282
+        $appTypes = $this->getAppTypes($app);
283
+        foreach ($types as $type) {
284
+            if (in_array($type, $appTypes, true)) {
285
+                return true;
286
+            }
287
+        }
288
+        return false;
289
+    }
290
+
291
+    /**
292
+     * get the types of an app
293
+     *
294
+     * @param string $app
295
+     * @return string[]
296
+     */
297
+    private function getAppTypes(string $app): array {
298
+        //load the cache
299
+        if (count($this->appTypes) === 0) {
300
+            $this->appTypes = $this->getAppConfig()->getValues(false, 'types') ?: [];
301
+        }
302
+
303
+        if (isset($this->appTypes[$app])) {
304
+            return explode(',', $this->appTypes[$app]);
305
+        }
306
+
307
+        return [];
308
+    }
309
+
310
+    /**
311
+     * @return array
312
+     */
313
+    public function getAutoDisabledApps(): array {
314
+        return $this->autoDisabledApps;
315
+    }
316
+
317
+    public function getAppRestriction(string $appId): array {
318
+        $values = $this->getEnabledAppsValues();
319
+
320
+        if (!isset($values[$appId])) {
321
+            return [];
322
+        }
323
+
324
+        if ($values[$appId] === 'yes' || $values[$appId] === 'no') {
325
+            return [];
326
+        }
327
+        return json_decode($values[$appId], true);
328
+    }
329
+
330
+    /**
331
+     * Check if an app is enabled for user
332
+     *
333
+     * @param string $appId
334
+     * @param \OCP\IUser|null $user (optional) if not defined, the currently logged in user will be used
335
+     * @return bool
336
+     */
337
+    public function isEnabledForUser($appId, $user = null) {
338
+        if ($this->isAlwaysEnabled($appId)) {
339
+            return true;
340
+        }
341
+        if ($user === null) {
342
+            $user = $this->userSession->getUser();
343
+        }
344
+        $enabledAppsValues = $this->getEnabledAppsValues();
345
+        if (isset($enabledAppsValues[$appId])) {
346
+            return $this->checkAppForUser($enabledAppsValues[$appId], $user);
347
+        } else {
348
+            return false;
349
+        }
350
+    }
351
+
352
+    private function checkAppForUser(string $enabled, ?IUser $user): bool {
353
+        if ($enabled === 'yes') {
354
+            return true;
355
+        } elseif ($user === null) {
356
+            return false;
357
+        } else {
358
+            if (empty($enabled)) {
359
+                return false;
360
+            }
361
+
362
+            $groupIds = json_decode($enabled);
363
+
364
+            if (!is_array($groupIds)) {
365
+                $jsonError = json_last_error();
366
+                $jsonErrorMsg = json_last_error_msg();
367
+                // this really should never happen (if it does, the admin should check the `enabled` key value via `occ config:list` because it's bogus for some reason)
368
+                $this->logger->warning('AppManager::checkAppForUser - can\'t decode group IDs listed in app\'s enabled config key: ' . print_r($enabled, true) . ' - JSON error (' . $jsonError . ') ' . $jsonErrorMsg);
369
+                return false;
370
+            }
371
+
372
+            $userGroups = $this->groupManager->getUserGroupIds($user);
373
+            foreach ($userGroups as $groupId) {
374
+                if (in_array($groupId, $groupIds, true)) {
375
+                    return true;
376
+                }
377
+            }
378
+            return false;
379
+        }
380
+    }
381
+
382
+    private function checkAppForGroups(string $enabled, IGroup $group): bool {
383
+        if ($enabled === 'yes') {
384
+            return true;
385
+        } else {
386
+            if (empty($enabled)) {
387
+                return false;
388
+            }
389
+
390
+            $groupIds = json_decode($enabled);
391
+
392
+            if (!is_array($groupIds)) {
393
+                $jsonError = json_last_error();
394
+                $jsonErrorMsg = json_last_error_msg();
395
+                // this really should never happen (if it does, the admin should check the `enabled` key value via `occ config:list` because it's bogus for some reason)
396
+                $this->logger->warning('AppManager::checkAppForGroups - can\'t decode group IDs listed in app\'s enabled config key: ' . print_r($enabled, true) . ' - JSON error (' . $jsonError . ') ' . $jsonErrorMsg);
397
+                return false;
398
+            }
399
+
400
+            return in_array($group->getGID(), $groupIds);
401
+        }
402
+    }
403
+
404
+    /**
405
+     * Check if an app is enabled in the instance
406
+     *
407
+     * Notice: This actually checks if the app is enabled and not only if it is installed.
408
+     *
409
+     * @param string $appId
410
+     */
411
+    public function isInstalled($appId): bool {
412
+        return $this->isEnabledForAnyone($appId);
413
+    }
414
+
415
+    public function isEnabledForAnyone(string $appId): bool {
416
+        $enabledAppsValues = $this->getEnabledAppsValues();
417
+        return isset($enabledAppsValues[$appId]);
418
+    }
419
+
420
+    /**
421
+     * Overwrite the `max-version` requirement for this app.
422
+     */
423
+    public function overwriteNextcloudRequirement(string $appId): void {
424
+        $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
425
+        if (!in_array($appId, $ignoreMaxApps, true)) {
426
+            $ignoreMaxApps[] = $appId;
427
+        }
428
+        $this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
429
+    }
430
+
431
+    /**
432
+     * Remove the `max-version` overwrite for this app.
433
+     * This means this app now again can not be enabled if the `max-version` is smaller than the current Nextcloud version.
434
+     */
435
+    public function removeOverwriteNextcloudRequirement(string $appId): void {
436
+        $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
437
+        $ignoreMaxApps = array_filter($ignoreMaxApps, fn (string $id) => $id !== $appId);
438
+        $this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
439
+    }
440
+
441
+    public function loadApp(string $app): void {
442
+        if (isset($this->loadedApps[$app])) {
443
+            return;
444
+        }
445
+        $this->loadedApps[$app] = true;
446
+        $appPath = \OC_App::getAppPath($app);
447
+        if ($appPath === false) {
448
+            return;
449
+        }
450
+        $eventLogger = \OC::$server->get(IEventLogger::class);
451
+        $eventLogger->start("bootstrap:load_app:$app", "Load app: $app");
452
+
453
+        // in case someone calls loadApp() directly
454
+        \OC_App::registerAutoloading($app, $appPath);
455
+
456
+        if (is_file($appPath . '/appinfo/app.php')) {
457
+            $this->logger->error('/appinfo/app.php is not supported anymore, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
458
+                'app' => $app,
459
+            ]);
460
+        }
461
+
462
+        $coordinator = \OCP\Server::get(Coordinator::class);
463
+        $coordinator->bootApp($app);
464
+
465
+        $eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it");
466
+        $info = $this->getAppInfo($app);
467
+        if (!empty($info['activity'])) {
468
+            $activityManager = \OC::$server->get(IActivityManager::class);
469
+            if (!empty($info['activity']['filters'])) {
470
+                foreach ($info['activity']['filters'] as $filter) {
471
+                    $activityManager->registerFilter($filter);
472
+                }
473
+            }
474
+            if (!empty($info['activity']['settings'])) {
475
+                foreach ($info['activity']['settings'] as $setting) {
476
+                    $activityManager->registerSetting($setting);
477
+                }
478
+            }
479
+            if (!empty($info['activity']['providers'])) {
480
+                foreach ($info['activity']['providers'] as $provider) {
481
+                    $activityManager->registerProvider($provider);
482
+                }
483
+            }
484
+        }
485
+
486
+        if (!empty($info['settings'])) {
487
+            $settingsManager = \OC::$server->get(ISettingsManager::class);
488
+            if (!empty($info['settings']['admin'])) {
489
+                foreach ($info['settings']['admin'] as $setting) {
490
+                    $settingsManager->registerSetting('admin', $setting);
491
+                }
492
+            }
493
+            if (!empty($info['settings']['admin-section'])) {
494
+                foreach ($info['settings']['admin-section'] as $section) {
495
+                    $settingsManager->registerSection('admin', $section);
496
+                }
497
+            }
498
+            if (!empty($info['settings']['personal'])) {
499
+                foreach ($info['settings']['personal'] as $setting) {
500
+                    $settingsManager->registerSetting('personal', $setting);
501
+                }
502
+            }
503
+            if (!empty($info['settings']['personal-section'])) {
504
+                foreach ($info['settings']['personal-section'] as $section) {
505
+                    $settingsManager->registerSection('personal', $section);
506
+                }
507
+            }
508
+        }
509
+
510
+        if (!empty($info['collaboration']['plugins'])) {
511
+            // deal with one or many plugin entries
512
+            $plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
513
+                [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
514
+            $collaboratorSearch = null;
515
+            $autoCompleteManager = null;
516
+            foreach ($plugins as $plugin) {
517
+                if ($plugin['@attributes']['type'] === 'collaborator-search') {
518
+                    $pluginInfo = [
519
+                        'shareType' => $plugin['@attributes']['share-type'],
520
+                        'class' => $plugin['@value'],
521
+                    ];
522
+                    $collaboratorSearch ??= \OC::$server->get(ICollaboratorSearch::class);
523
+                    $collaboratorSearch->registerPlugin($pluginInfo);
524
+                } elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
525
+                    $autoCompleteManager ??= \OC::$server->get(IAutoCompleteManager::class);
526
+                    $autoCompleteManager->registerSorter($plugin['@value']);
527
+                }
528
+            }
529
+        }
530
+        $eventLogger->end("bootstrap:load_app:$app:info");
531
+
532
+        $eventLogger->end("bootstrap:load_app:$app");
533
+    }
534
+
535
+    /**
536
+     * Check if an app is loaded
537
+     * @param string $app app id
538
+     * @since 26.0.0
539
+     */
540
+    public function isAppLoaded(string $app): bool {
541
+        return isset($this->loadedApps[$app]);
542
+    }
543
+
544
+    /**
545
+     * Enable an app for every user
546
+     *
547
+     * @param string $appId
548
+     * @param bool $forceEnable
549
+     * @throws AppPathNotFoundException
550
+     * @throws \InvalidArgumentException if the application is not installed yet
551
+     */
552
+    public function enableApp(string $appId, bool $forceEnable = false): void {
553
+        // Check if app exists
554
+        $this->getAppPath($appId);
555
+
556
+        if ($this->config->getAppValue($appId, 'installed_version', '') === '') {
557
+            throw new \InvalidArgumentException("$appId is not installed, cannot be enabled.");
558
+        }
559
+
560
+        if ($forceEnable) {
561
+            $this->overwriteNextcloudRequirement($appId);
562
+        }
563
+
564
+        $this->enabledAppsCache[$appId] = 'yes';
565
+        $this->getAppConfig()->setValue($appId, 'enabled', 'yes');
566
+        $this->dispatcher->dispatchTyped(new AppEnableEvent($appId));
567
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
568
+            ManagerEvent::EVENT_APP_ENABLE, $appId
569
+        ));
570
+        $this->clearAppsCache();
571
+    }
572
+
573
+    /**
574
+     * Whether a list of types contains a protected app type
575
+     *
576
+     * @param string[] $types
577
+     * @return bool
578
+     */
579
+    public function hasProtectedAppType($types) {
580
+        if (empty($types)) {
581
+            return false;
582
+        }
583
+
584
+        $protectedTypes = array_intersect($this->protectedAppTypes, $types);
585
+        return !empty($protectedTypes);
586
+    }
587
+
588
+    /**
589
+     * Enable an app only for specific groups
590
+     *
591
+     * @param string $appId
592
+     * @param IGroup[] $groups
593
+     * @param bool $forceEnable
594
+     * @throws \InvalidArgumentException if app can't be enabled for groups
595
+     * @throws AppPathNotFoundException
596
+     */
597
+    public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void {
598
+        // Check if app exists
599
+        $this->getAppPath($appId);
600
+
601
+        $info = $this->getAppInfo($appId);
602
+        if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) {
603
+            throw new \InvalidArgumentException("$appId can't be enabled for groups.");
604
+        }
605
+
606
+        if ($this->config->getAppValue($appId, 'installed_version', '') === '') {
607
+            throw new \InvalidArgumentException("$appId is not installed, cannot be enabled.");
608
+        }
609
+
610
+        if ($forceEnable) {
611
+            $this->overwriteNextcloudRequirement($appId);
612
+        }
613
+
614
+        /** @var string[] $groupIds */
615
+        $groupIds = array_map(function ($group) {
616
+            /** @var IGroup $group */
617
+            return ($group instanceof IGroup)
618
+                ? $group->getGID()
619
+                : $group;
620
+        }, $groups);
621
+
622
+        $this->enabledAppsCache[$appId] = json_encode($groupIds);
623
+        $this->getAppConfig()->setValue($appId, 'enabled', json_encode($groupIds));
624
+        $this->dispatcher->dispatchTyped(new AppEnableEvent($appId, $groupIds));
625
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
626
+            ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
627
+        ));
628
+        $this->clearAppsCache();
629
+    }
630
+
631
+    /**
632
+     * Disable an app for every user
633
+     *
634
+     * @param string $appId
635
+     * @param bool $automaticDisabled
636
+     * @throws \Exception if app can't be disabled
637
+     */
638
+    public function disableApp($appId, $automaticDisabled = false): void {
639
+        if ($this->isAlwaysEnabled($appId)) {
640
+            throw new \Exception("$appId can't be disabled.");
641
+        }
642
+
643
+        if ($automaticDisabled) {
644
+            $previousSetting = $this->getAppConfig()->getValue($appId, 'enabled', 'yes');
645
+            if ($previousSetting !== 'yes' && $previousSetting !== 'no') {
646
+                $previousSetting = json_decode($previousSetting, true);
647
+            }
648
+            $this->autoDisabledApps[$appId] = $previousSetting;
649
+        }
650
+
651
+        unset($this->enabledAppsCache[$appId]);
652
+        $this->getAppConfig()->setValue($appId, 'enabled', 'no');
653
+
654
+        // run uninstall steps
655
+        $appData = $this->getAppInfo($appId);
656
+        if (!is_null($appData)) {
657
+            \OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
658
+        }
659
+
660
+        $this->dispatcher->dispatchTyped(new AppDisableEvent($appId));
661
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
662
+            ManagerEvent::EVENT_APP_DISABLE, $appId
663
+        ));
664
+        $this->clearAppsCache();
665
+    }
666
+
667
+    /**
668
+     * Get the directory for the given app.
669
+     *
670
+     * @throws AppPathNotFoundException if app folder can't be found
671
+     */
672
+    public function getAppPath(string $appId): string {
673
+        $appPath = \OC_App::getAppPath($appId);
674
+        if ($appPath === false) {
675
+            throw new AppPathNotFoundException('Could not find path for ' . $appId);
676
+        }
677
+        return $appPath;
678
+    }
679
+
680
+    /**
681
+     * Get the web path for the given app.
682
+     *
683
+     * @param string $appId
684
+     * @return string
685
+     * @throws AppPathNotFoundException if app path can't be found
686
+     */
687
+    public function getAppWebPath(string $appId): string {
688
+        $appWebPath = \OC_App::getAppWebPath($appId);
689
+        if ($appWebPath === false) {
690
+            throw new AppPathNotFoundException('Could not find web path for ' . $appId);
691
+        }
692
+        return $appWebPath;
693
+    }
694
+
695
+    /**
696
+     * Clear the cached list of apps when enabling/disabling an app
697
+     */
698
+    public function clearAppsCache(): void {
699
+        $this->appInfos = [];
700
+    }
701
+
702
+    /**
703
+     * Returns a list of apps that need upgrade
704
+     *
705
+     * @param string $version Nextcloud version as array of version components
706
+     * @return array list of app info from apps that need an upgrade
707
+     *
708
+     * @internal
709
+     */
710
+    public function getAppsNeedingUpgrade($version) {
711
+        $appsToUpgrade = [];
712
+        $apps = $this->getEnabledApps();
713
+        foreach ($apps as $appId) {
714
+            $appInfo = $this->getAppInfo($appId);
715
+            $appDbVersion = $this->getAppConfig()->getValue($appId, 'installed_version');
716
+            if ($appDbVersion
717
+                && isset($appInfo['version'])
718
+                && version_compare($appInfo['version'], $appDbVersion, '>')
719
+                && \OC_App::isAppCompatible($version, $appInfo)
720
+            ) {
721
+                $appsToUpgrade[] = $appInfo;
722
+            }
723
+        }
724
+
725
+        return $appsToUpgrade;
726
+    }
727
+
728
+    /**
729
+     * Returns the app information from "appinfo/info.xml".
730
+     *
731
+     * @param string|null $lang
732
+     * @return array|null app info
733
+     */
734
+    public function getAppInfo(string $appId, bool $path = false, $lang = null) {
735
+        if ($path) {
736
+            throw new \InvalidArgumentException('Calling IAppManager::getAppInfo() with a path is no longer supported. Please call IAppManager::getAppInfoByPath() instead and verify that the path is good before calling.');
737
+        }
738
+        if ($lang === null && isset($this->appInfos[$appId])) {
739
+            return $this->appInfos[$appId];
740
+        }
741
+        try {
742
+            $appPath = $this->getAppPath($appId);
743
+        } catch (AppPathNotFoundException) {
744
+            return null;
745
+        }
746
+        $file = $appPath . '/appinfo/info.xml';
747
+
748
+        $data = $this->getAppInfoByPath($file, $lang);
749
+
750
+        if ($lang === null) {
751
+            $this->appInfos[$appId] = $data;
752
+        }
753
+
754
+        return $data;
755
+    }
756
+
757
+    public function getAppInfoByPath(string $path, ?string $lang = null): ?array {
758
+        if (!str_ends_with($path, '/appinfo/info.xml')) {
759
+            return null;
760
+        }
761
+
762
+        $parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
763
+        $data = $parser->parse($path);
764
+
765
+        if (is_array($data)) {
766
+            $data = \OC_App::parseAppInfo($data, $lang);
767
+        }
768
+
769
+        return $data;
770
+    }
771
+
772
+    public function getAppVersion(string $appId, bool $useCache = true): string {
773
+        if (!$useCache || !isset($this->appVersions[$appId])) {
774
+            if ($appId === 'core') {
775
+                $this->appVersions[$appId] = $this->serverVersion->getVersionString();
776
+            } else {
777
+                $appInfo = $this->getAppInfo($appId);
778
+                $this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
779
+            }
780
+        }
781
+        return $this->appVersions[$appId];
782
+    }
783
+
784
+    /**
785
+     * Returns the installed versions of all apps
786
+     *
787
+     * @return array<string, string>
788
+     */
789
+    public function getAppInstalledVersions(bool $onlyEnabled = false): array {
790
+        return $this->getAppConfig()->getAppInstalledVersions($onlyEnabled);
791
+    }
792
+
793
+    /**
794
+     * Returns a list of apps incompatible with the given version
795
+     *
796
+     * @param string $version Nextcloud version as array of version components
797
+     *
798
+     * @return array list of app info from incompatible apps
799
+     *
800
+     * @internal
801
+     */
802
+    public function getIncompatibleApps(string $version): array {
803
+        $apps = $this->getEnabledApps();
804
+        $incompatibleApps = [];
805
+        foreach ($apps as $appId) {
806
+            $info = $this->getAppInfo($appId);
807
+            if ($info === null) {
808
+                $incompatibleApps[] = ['id' => $appId, 'name' => $appId];
809
+            } elseif (!\OC_App::isAppCompatible($version, $info)) {
810
+                $incompatibleApps[] = $info;
811
+            }
812
+        }
813
+        return $incompatibleApps;
814
+    }
815
+
816
+    /**
817
+     * @inheritdoc
818
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
819
+     */
820
+    public function isShipped($appId) {
821
+        $this->loadShippedJson();
822
+        return in_array($appId, $this->shippedApps, true);
823
+    }
824
+
825
+    private function isAlwaysEnabled(string $appId): bool {
826
+        if ($appId === 'core') {
827
+            return true;
828
+        }
829
+
830
+        $alwaysEnabled = $this->getAlwaysEnabledApps();
831
+        return in_array($appId, $alwaysEnabled, true);
832
+    }
833
+
834
+    /**
835
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
836
+     * @throws \Exception
837
+     */
838
+    private function loadShippedJson(): void {
839
+        if ($this->shippedApps === null) {
840
+            $shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
841
+            if (!file_exists($shippedJson)) {
842
+                throw new \Exception("File not found: $shippedJson");
843
+            }
844
+            $content = json_decode(file_get_contents($shippedJson), true);
845
+            $this->shippedApps = $content['shippedApps'];
846
+            $this->alwaysEnabled = $content['alwaysEnabled'];
847
+            $this->defaultEnabled = $content['defaultEnabled'];
848
+        }
849
+    }
850
+
851
+    /**
852
+     * @inheritdoc
853
+     */
854
+    public function getAlwaysEnabledApps() {
855
+        $this->loadShippedJson();
856
+        return $this->alwaysEnabled;
857
+    }
858
+
859
+    /**
860
+     * @inheritdoc
861
+     */
862
+    public function isDefaultEnabled(string $appId): bool {
863
+        return (in_array($appId, $this->getDefaultEnabledApps()));
864
+    }
865
+
866
+    /**
867
+     * @inheritdoc
868
+     */
869
+    public function getDefaultEnabledApps(): array {
870
+        $this->loadShippedJson();
871
+
872
+        return $this->defaultEnabled;
873
+    }
874
+
875
+    /**
876
+     * @inheritdoc
877
+     */
878
+    public function getDefaultAppForUser(?IUser $user = null, bool $withFallbacks = true): string {
879
+        $id = $this->getNavigationManager()->getDefaultEntryIdForUser($user, $withFallbacks);
880
+        $entry = $this->getNavigationManager()->get($id);
881
+        return (string)$entry['app'];
882
+    }
883
+
884
+    /**
885
+     * @inheritdoc
886
+     */
887
+    public function getDefaultApps(): array {
888
+        $ids = $this->getNavigationManager()->getDefaultEntryIds();
889
+
890
+        return array_values(array_unique(array_map(function (string $id) {
891
+            $entry = $this->getNavigationManager()->get($id);
892
+            return (string)$entry['app'];
893
+        }, $ids)));
894
+    }
895
+
896
+    /**
897
+     * @inheritdoc
898
+     */
899
+    public function setDefaultApps(array $defaultApps): void {
900
+        $entries = $this->getNavigationManager()->getAll();
901
+        $ids = [];
902
+        foreach ($defaultApps as $defaultApp) {
903
+            foreach ($entries as $entry) {
904
+                if ((string)$entry['app'] === $defaultApp) {
905
+                    $ids[] = (string)$entry['id'];
906
+                    break;
907
+                }
908
+            }
909
+        }
910
+        $this->getNavigationManager()->setDefaultEntryIds($ids);
911
+    }
912
+
913
+    public function isBackendRequired(string $backend): bool {
914
+        foreach ($this->appInfos as $appInfo) {
915
+            foreach ($appInfo['dependencies']['backend'] as $appBackend) {
916
+                if ($backend === $appBackend) {
917
+                    return true;
918
+                }
919
+            }
920
+        }
921
+
922
+        return false;
923
+    }
924
+
925
+    /**
926
+     * Clean the appId from forbidden characters
927
+     *
928
+     * @psalm-taint-escape callable
929
+     * @psalm-taint-escape cookie
930
+     * @psalm-taint-escape file
931
+     * @psalm-taint-escape has_quotes
932
+     * @psalm-taint-escape header
933
+     * @psalm-taint-escape html
934
+     * @psalm-taint-escape include
935
+     * @psalm-taint-escape ldap
936
+     * @psalm-taint-escape shell
937
+     * @psalm-taint-escape sql
938
+     * @psalm-taint-escape unserialize
939
+     */
940
+    public function cleanAppId(string $app): string {
941
+        /* Only lowercase alphanumeric is allowed */
942
+        return preg_replace('/(^[0-9_]|[^a-z0-9_]+|_$)/', '', $app);
943
+    }
944 944
 }
Please login to merge, or discard this patch.
lib/private/AppConfig.php 1 patch
Indentation   +1638 added lines, -1638 removed lines patch added patch discarded remove patch
@@ -46,1642 +46,1642 @@
 block discarded – undo
46 46
  * @since 29.0.0 - Supporting types and lazy loading
47 47
  */
48 48
 class AppConfig implements IAppConfig {
49
-	private const APP_MAX_LENGTH = 32;
50
-	private const KEY_MAX_LENGTH = 64;
51
-	private const ENCRYPTION_PREFIX = '$AppConfigEncryption$';
52
-	private const ENCRYPTION_PREFIX_LENGTH = 21; // strlen(self::ENCRYPTION_PREFIX)
53
-
54
-	/** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
55
-	private array $fastCache = [];   // cache for normal config keys
56
-	/** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
57
-	private array $lazyCache = [];   // cache for lazy config keys
58
-	/** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
59
-	private array $valueTypes = [];  // type for all config values
60
-	private bool $fastLoaded = false;
61
-	private bool $lazyLoaded = false;
62
-	/** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
63
-	private array $configLexiconDetails = [];
64
-
65
-	/** @var ?array<string, string> */
66
-	private ?array $appVersionsCache = null;
67
-
68
-	public function __construct(
69
-		protected IDBConnection $connection,
70
-		protected LoggerInterface $logger,
71
-		protected ICrypto $crypto,
72
-	) {
73
-	}
74
-
75
-	/**
76
-	 * @inheritDoc
77
-	 *
78
-	 * @return list<string> list of app ids
79
-	 * @since 7.0.0
80
-	 */
81
-	public function getApps(): array {
82
-		$this->loadConfigAll();
83
-		$apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
84
-		sort($apps);
85
-
86
-		return array_values(array_unique($apps));
87
-	}
88
-
89
-	/**
90
-	 * @inheritDoc
91
-	 *
92
-	 * @param string $app id of the app
93
-	 *
94
-	 * @return list<string> list of stored config keys
95
-	 * @since 29.0.0
96
-	 */
97
-	public function getKeys(string $app): array {
98
-		$this->assertParams($app);
99
-		$this->loadConfigAll($app);
100
-		$keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
101
-		sort($keys);
102
-
103
-		return array_values(array_unique($keys));
104
-	}
105
-
106
-	/**
107
-	 * @inheritDoc
108
-	 *
109
-	 * @param string $app id of the app
110
-	 * @param string $key config key
111
-	 * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
112
-	 *
113
-	 * @return bool TRUE if key exists
114
-	 * @since 7.0.0
115
-	 * @since 29.0.0 Added the $lazy argument
116
-	 */
117
-	public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
118
-		$this->assertParams($app, $key);
119
-		$this->loadConfig($app, $lazy);
120
-
121
-		if ($lazy === null) {
122
-			$appCache = $this->getAllValues($app);
123
-			return isset($appCache[$key]);
124
-		}
125
-
126
-		if ($lazy) {
127
-			return isset($this->lazyCache[$app][$key]);
128
-		}
129
-
130
-		return isset($this->fastCache[$app][$key]);
131
-	}
132
-
133
-	/**
134
-	 * @param string $app id of the app
135
-	 * @param string $key config key
136
-	 * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
137
-	 *
138
-	 * @return bool
139
-	 * @throws AppConfigUnknownKeyException if config key is not known
140
-	 * @since 29.0.0
141
-	 */
142
-	public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
143
-		$this->assertParams($app, $key);
144
-		$this->loadConfig(null, $lazy);
145
-
146
-		if (!isset($this->valueTypes[$app][$key])) {
147
-			throw new AppConfigUnknownKeyException('unknown config key');
148
-		}
149
-
150
-		return $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key]);
151
-	}
152
-
153
-	/**
154
-	 * @inheritDoc
155
-	 *
156
-	 * @param string $app if of the app
157
-	 * @param string $key config key
158
-	 *
159
-	 * @return bool TRUE if config is lazy loaded
160
-	 * @throws AppConfigUnknownKeyException if config key is not known
161
-	 * @see IAppConfig for details about lazy loading
162
-	 * @since 29.0.0
163
-	 */
164
-	public function isLazy(string $app, string $key): bool {
165
-		// there is a huge probability the non-lazy config are already loaded
166
-		if ($this->hasKey($app, $key, false)) {
167
-			return false;
168
-		}
169
-
170
-		// key not found, we search in the lazy config
171
-		if ($this->hasKey($app, $key, true)) {
172
-			return true;
173
-		}
174
-
175
-		throw new AppConfigUnknownKeyException('unknown config key');
176
-	}
177
-
178
-
179
-	/**
180
-	 * @inheritDoc
181
-	 *
182
-	 * @param string $app id of the app
183
-	 * @param string $prefix config keys prefix to search
184
-	 * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
185
-	 *
186
-	 * @return array<string, string|int|float|bool|array> [configKey => configValue]
187
-	 * @since 29.0.0
188
-	 */
189
-	public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
190
-		$this->assertParams($app, $prefix);
191
-		// if we want to filter values, we need to get sensitivity
192
-		$this->loadConfigAll($app);
193
-		// array_merge() will remove numeric keys (here config keys), so addition arrays instead
194
-		$values = $this->formatAppValues($app, ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []));
195
-		$values = array_filter(
196
-			$values,
197
-			function (string $key) use ($prefix): bool {
198
-				return str_starts_with($key, $prefix); // filter values based on $prefix
199
-			}, ARRAY_FILTER_USE_KEY
200
-		);
201
-
202
-		if (!$filtered) {
203
-			return $values;
204
-		}
205
-
206
-		/**
207
-		 * Using the old (deprecated) list of sensitive values.
208
-		 */
209
-		foreach ($this->getSensitiveKeys($app) as $sensitiveKeyExp) {
210
-			$sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
211
-			foreach ($sensitiveKeys as $sensitiveKey) {
212
-				$this->valueTypes[$app][$sensitiveKey] = ($this->valueTypes[$app][$sensitiveKey] ?? 0) | self::VALUE_SENSITIVE;
213
-			}
214
-		}
215
-
216
-		$result = [];
217
-		foreach ($values as $key => $value) {
218
-			$result[$key] = $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key] ?? 0) ? IConfig::SENSITIVE_VALUE : $value;
219
-		}
220
-
221
-		return $result;
222
-	}
223
-
224
-	/**
225
-	 * @inheritDoc
226
-	 *
227
-	 * @param string $key config key
228
-	 * @param bool $lazy search within lazy loaded config
229
-	 * @param int|null $typedAs enforce type for the returned values ({@see self::VALUE_STRING} and others)
230
-	 *
231
-	 * @return array<string, string|int|float|bool|array> [appId => configValue]
232
-	 * @since 29.0.0
233
-	 */
234
-	public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array {
235
-		$this->assertParams('', $key, true);
236
-		$this->loadConfig(null, $lazy);
237
-
238
-		/** @var array<array-key, array<array-key, mixed>> $cache */
239
-		if ($lazy) {
240
-			$cache = $this->lazyCache;
241
-		} else {
242
-			$cache = $this->fastCache;
243
-		}
244
-
245
-		$values = [];
246
-		foreach (array_keys($cache) as $app) {
247
-			if (isset($cache[$app][$key])) {
248
-				$values[$app] = $this->convertTypedValue($cache[$app][$key], $typedAs ?? $this->getValueType((string)$app, $key, $lazy));
249
-			}
250
-		}
251
-
252
-		return $values;
253
-	}
254
-
255
-
256
-	/**
257
-	 * Get the config value as string.
258
-	 * If the value does not exist the given default will be returned.
259
-	 *
260
-	 * Set lazy to `null` to ignore it and get the value from either source.
261
-	 *
262
-	 * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
263
-	 *
264
-	 * @param string $app id of the app
265
-	 * @param string $key config key
266
-	 * @param string $default config value
267
-	 * @param null|bool $lazy get config as lazy loaded or not. can be NULL
268
-	 *
269
-	 * @return string the value or $default
270
-	 * @internal
271
-	 * @since 29.0.0
272
-	 * @see IAppConfig for explanation about lazy loading
273
-	 * @see getValueString()
274
-	 * @see getValueInt()
275
-	 * @see getValueFloat()
276
-	 * @see getValueBool()
277
-	 * @see getValueArray()
278
-	 */
279
-	public function getValueMixed(
280
-		string $app,
281
-		string $key,
282
-		string $default = '',
283
-		?bool $lazy = false,
284
-	): string {
285
-		try {
286
-			$lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
287
-		} catch (AppConfigUnknownKeyException $e) {
288
-			return $default;
289
-		}
290
-
291
-		return $this->getTypedValue(
292
-			$app,
293
-			$key,
294
-			$default,
295
-			$lazy,
296
-			self::VALUE_MIXED
297
-		);
298
-	}
299
-
300
-	/**
301
-	 * @inheritDoc
302
-	 *
303
-	 * @param string $app id of the app
304
-	 * @param string $key config key
305
-	 * @param string $default default value
306
-	 * @param bool $lazy search within lazy loaded config
307
-	 *
308
-	 * @return string stored config value or $default if not set in database
309
-	 * @throws InvalidArgumentException if one of the argument format is invalid
310
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
311
-	 * @since 29.0.0
312
-	 * @see IAppConfig for explanation about lazy loading
313
-	 */
314
-	public function getValueString(
315
-		string $app,
316
-		string $key,
317
-		string $default = '',
318
-		bool $lazy = false,
319
-	): string {
320
-		return $this->getTypedValue($app, $key, $default, $lazy, self::VALUE_STRING);
321
-	}
322
-
323
-	/**
324
-	 * @inheritDoc
325
-	 *
326
-	 * @param string $app id of the app
327
-	 * @param string $key config key
328
-	 * @param int $default default value
329
-	 * @param bool $lazy search within lazy loaded config
330
-	 *
331
-	 * @return int stored config value or $default if not set in database
332
-	 * @throws InvalidArgumentException if one of the argument format is invalid
333
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
334
-	 * @since 29.0.0
335
-	 * @see IAppConfig for explanation about lazy loading
336
-	 */
337
-	public function getValueInt(
338
-		string $app,
339
-		string $key,
340
-		int $default = 0,
341
-		bool $lazy = false,
342
-	): int {
343
-		return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
344
-	}
345
-
346
-	/**
347
-	 * @inheritDoc
348
-	 *
349
-	 * @param string $app id of the app
350
-	 * @param string $key config key
351
-	 * @param float $default default value
352
-	 * @param bool $lazy search within lazy loaded config
353
-	 *
354
-	 * @return float stored config value or $default if not set in database
355
-	 * @throws InvalidArgumentException if one of the argument format is invalid
356
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
357
-	 * @since 29.0.0
358
-	 * @see IAppConfig for explanation about lazy loading
359
-	 */
360
-	public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
361
-		return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
362
-	}
363
-
364
-	/**
365
-	 * @inheritDoc
366
-	 *
367
-	 * @param string $app id of the app
368
-	 * @param string $key config key
369
-	 * @param bool $default default value
370
-	 * @param bool $lazy search within lazy loaded config
371
-	 *
372
-	 * @return bool stored config value or $default if not set in database
373
-	 * @throws InvalidArgumentException if one of the argument format is invalid
374
-	 * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
375
-	 * @since 29.0.0
376
-	 * @see IAppConfig for explanation about lazy loading
377
-	 */
378
-	public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
379
-		$b = strtolower($this->getTypedValue($app, $key, $default ? 'true' : 'false', $lazy, self::VALUE_BOOL));
380
-		return in_array($b, ['1', 'true', 'yes', 'on']);
381
-	}
382
-
383
-	/**
384
-	 * @inheritDoc
385
-	 *
386
-	 * @param string $app id of the app
387
-	 * @param string $key config key
388
-	 * @param array $default default value
389
-	 * @param bool $lazy search within lazy loaded config
390
-	 *
391
-	 * @return array 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 getValueArray(
398
-		string $app,
399
-		string $key,
400
-		array $default = [],
401
-		bool $lazy = false,
402
-	): array {
403
-		try {
404
-			$defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
405
-			$value = json_decode($this->getTypedValue($app, $key, $defaultJson, $lazy, self::VALUE_ARRAY), true, flags: JSON_THROW_ON_ERROR);
406
-
407
-			return is_array($value) ? $value : [];
408
-		} catch (JsonException) {
409
-			return [];
410
-		}
411
-	}
412
-
413
-	/**
414
-	 * @param string $app id of the app
415
-	 * @param string $key config key
416
-	 * @param string $default default value
417
-	 * @param bool $lazy search within lazy loaded config
418
-	 * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT}{@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
419
-	 *
420
-	 * @return string
421
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
422
-	 * @throws InvalidArgumentException
423
-	 */
424
-	private function getTypedValue(
425
-		string $app,
426
-		string $key,
427
-		string $default,
428
-		bool $lazy,
429
-		int $type,
430
-	): string {
431
-		$this->assertParams($app, $key, valueType: $type);
432
-		if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default)) {
433
-			return $default; // returns default if strictness of lexicon is set to WARNING (block and report)
434
-		}
435
-		$this->loadConfig($app, $lazy);
436
-
437
-		/**
438
-		 * We ignore check if mixed type is requested.
439
-		 * If type of stored value is set as mixed, we don't filter.
440
-		 * If type of stored value is defined, we compare with the one requested.
441
-		 */
442
-		$knownType = $this->valueTypes[$app][$key] ?? 0;
443
-		if (!$this->isTyped(self::VALUE_MIXED, $type)
444
-			&& $knownType > 0
445
-			&& !$this->isTyped(self::VALUE_MIXED, $knownType)
446
-			&& !$this->isTyped($type, $knownType)) {
447
-			$this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
448
-			throw new AppConfigTypeConflictException('conflict with value type from database');
449
-		}
450
-
451
-		/**
452
-		 * - the pair $app/$key cannot exist in both array,
453
-		 * - we should still return an existing non-lazy value even if current method
454
-		 *   is called with $lazy is true
455
-		 *
456
-		 * This way, lazyCache will be empty until the load for lazy config value is requested.
457
-		 */
458
-		if (isset($this->lazyCache[$app][$key])) {
459
-			$value = $this->lazyCache[$app][$key];
460
-		} elseif (isset($this->fastCache[$app][$key])) {
461
-			$value = $this->fastCache[$app][$key];
462
-		} else {
463
-			return $default;
464
-		}
465
-
466
-		$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $knownType);
467
-		if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
468
-			// Only decrypt values that are stored encrypted
469
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
470
-		}
471
-
472
-		return $value;
473
-	}
474
-
475
-	/**
476
-	 * @inheritDoc
477
-	 *
478
-	 * @param string $app id of the app
479
-	 * @param string $key config key
480
-	 *
481
-	 * @return int type of the value
482
-	 * @throws AppConfigUnknownKeyException if config key is not known
483
-	 * @since 29.0.0
484
-	 * @see VALUE_STRING
485
-	 * @see VALUE_INT
486
-	 * @see VALUE_FLOAT
487
-	 * @see VALUE_BOOL
488
-	 * @see VALUE_ARRAY
489
-	 */
490
-	public function getValueType(string $app, string $key, ?bool $lazy = null): int {
491
-		$type = self::VALUE_MIXED;
492
-		$ignorable = $lazy ?? false;
493
-		$this->matchAndApplyLexiconDefinition($app, $key, $ignorable, $type);
494
-		if ($type !== self::VALUE_MIXED) {
495
-			// a modified $type means config key is set in Lexicon
496
-			return $type;
497
-		}
498
-
499
-		$this->assertParams($app, $key);
500
-		$this->loadConfig($app, $lazy);
501
-
502
-		if (!isset($this->valueTypes[$app][$key])) {
503
-			throw new AppConfigUnknownKeyException('unknown config key');
504
-		}
505
-
506
-		$type = $this->valueTypes[$app][$key];
507
-		$type &= ~self::VALUE_SENSITIVE;
508
-		return $type;
509
-	}
510
-
511
-
512
-	/**
513
-	 * Store a config key and its value in database as VALUE_MIXED
514
-	 *
515
-	 * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
516
-	 *
517
-	 * @param string $app id of the app
518
-	 * @param string $key config key
519
-	 * @param string $value config value
520
-	 * @param bool $lazy set config as lazy loaded
521
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
522
-	 *
523
-	 * @return bool TRUE if value was different, therefor updated in database
524
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED
525
-	 * @internal
526
-	 * @since 29.0.0
527
-	 * @see IAppConfig for explanation about lazy loading
528
-	 * @see setValueString()
529
-	 * @see setValueInt()
530
-	 * @see setValueFloat()
531
-	 * @see setValueBool()
532
-	 * @see setValueArray()
533
-	 */
534
-	public function setValueMixed(
535
-		string $app,
536
-		string $key,
537
-		string $value,
538
-		bool $lazy = false,
539
-		bool $sensitive = false,
540
-	): bool {
541
-		return $this->setTypedValue(
542
-			$app,
543
-			$key,
544
-			$value,
545
-			$lazy,
546
-			self::VALUE_MIXED | ($sensitive ? self::VALUE_SENSITIVE : 0)
547
-		);
548
-	}
549
-
550
-
551
-	/**
552
-	 * @inheritDoc
553
-	 *
554
-	 * @param string $app id of the app
555
-	 * @param string $key config key
556
-	 * @param string $value config value
557
-	 * @param bool $lazy set config as lazy loaded
558
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
559
-	 *
560
-	 * @return bool TRUE if value was different, therefor updated in database
561
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
562
-	 * @since 29.0.0
563
-	 * @see IAppConfig for explanation about lazy loading
564
-	 */
565
-	public function setValueString(
566
-		string $app,
567
-		string $key,
568
-		string $value,
569
-		bool $lazy = false,
570
-		bool $sensitive = false,
571
-	): bool {
572
-		return $this->setTypedValue(
573
-			$app,
574
-			$key,
575
-			$value,
576
-			$lazy,
577
-			self::VALUE_STRING | ($sensitive ? self::VALUE_SENSITIVE : 0)
578
-		);
579
-	}
580
-
581
-	/**
582
-	 * @inheritDoc
583
-	 *
584
-	 * @param string $app id of the app
585
-	 * @param string $key config key
586
-	 * @param int $value config value
587
-	 * @param bool $lazy set config as lazy loaded
588
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
589
-	 *
590
-	 * @return bool TRUE if value was different, therefor updated in database
591
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
592
-	 * @since 29.0.0
593
-	 * @see IAppConfig for explanation about lazy loading
594
-	 */
595
-	public function setValueInt(
596
-		string $app,
597
-		string $key,
598
-		int $value,
599
-		bool $lazy = false,
600
-		bool $sensitive = false,
601
-	): bool {
602
-		if ($value > 2000000000) {
603
-			$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.');
604
-		}
605
-
606
-		return $this->setTypedValue(
607
-			$app,
608
-			$key,
609
-			(string)$value,
610
-			$lazy,
611
-			self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
612
-		);
613
-	}
614
-
615
-	/**
616
-	 * @inheritDoc
617
-	 *
618
-	 * @param string $app id of the app
619
-	 * @param string $key config key
620
-	 * @param float $value config value
621
-	 * @param bool $lazy set config as lazy loaded
622
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
623
-	 *
624
-	 * @return bool TRUE if value was different, therefor updated in database
625
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
626
-	 * @since 29.0.0
627
-	 * @see IAppConfig for explanation about lazy loading
628
-	 */
629
-	public function setValueFloat(
630
-		string $app,
631
-		string $key,
632
-		float $value,
633
-		bool $lazy = false,
634
-		bool $sensitive = false,
635
-	): bool {
636
-		return $this->setTypedValue(
637
-			$app,
638
-			$key,
639
-			(string)$value,
640
-			$lazy,
641
-			self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
642
-		);
643
-	}
644
-
645
-	/**
646
-	 * @inheritDoc
647
-	 *
648
-	 * @param string $app id of the app
649
-	 * @param string $key config key
650
-	 * @param bool $value config value
651
-	 * @param bool $lazy set config as lazy loaded
652
-	 *
653
-	 * @return bool TRUE if value was different, therefor updated in database
654
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
655
-	 * @since 29.0.0
656
-	 * @see IAppConfig for explanation about lazy loading
657
-	 */
658
-	public function setValueBool(
659
-		string $app,
660
-		string $key,
661
-		bool $value,
662
-		bool $lazy = false,
663
-	): bool {
664
-		return $this->setTypedValue(
665
-			$app,
666
-			$key,
667
-			($value) ? '1' : '0',
668
-			$lazy,
669
-			self::VALUE_BOOL
670
-		);
671
-	}
672
-
673
-	/**
674
-	 * @inheritDoc
675
-	 *
676
-	 * @param string $app id of the app
677
-	 * @param string $key config key
678
-	 * @param array $value config value
679
-	 * @param bool $lazy set config as lazy loaded
680
-	 * @param bool $sensitive if TRUE value will be hidden when listing config values.
681
-	 *
682
-	 * @return bool TRUE if value was different, therefor updated in database
683
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
684
-	 * @throws JsonException
685
-	 * @since 29.0.0
686
-	 * @see IAppConfig for explanation about lazy loading
687
-	 */
688
-	public function setValueArray(
689
-		string $app,
690
-		string $key,
691
-		array $value,
692
-		bool $lazy = false,
693
-		bool $sensitive = false,
694
-	): bool {
695
-		try {
696
-			return $this->setTypedValue(
697
-				$app,
698
-				$key,
699
-				json_encode($value, JSON_THROW_ON_ERROR),
700
-				$lazy,
701
-				self::VALUE_ARRAY | ($sensitive ? self::VALUE_SENSITIVE : 0)
702
-			);
703
-		} catch (JsonException $e) {
704
-			$this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
705
-			throw $e;
706
-		}
707
-	}
708
-
709
-	/**
710
-	 * Store a config key and its value in database
711
-	 *
712
-	 * If config key is already known with the exact same config value and same sensitive/lazy status, the
713
-	 * database is not updated. If config value was previously stored as sensitive, status will not be
714
-	 * altered.
715
-	 *
716
-	 * @param string $app id of the app
717
-	 * @param string $key config key
718
-	 * @param string $value config value
719
-	 * @param bool $lazy config set as lazy loaded
720
-	 * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
721
-	 *
722
-	 * @return bool TRUE if value was updated in database
723
-	 * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
724
-	 * @see IAppConfig for explanation about lazy loading
725
-	 */
726
-	private function setTypedValue(
727
-		string $app,
728
-		string $key,
729
-		string $value,
730
-		bool $lazy,
731
-		int $type,
732
-	): bool {
733
-		$this->assertParams($app, $key);
734
-		if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
735
-			return false; // returns false as database is not updated
736
-		}
737
-		$this->loadConfig(null, $lazy);
738
-
739
-		$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
740
-		$inserted = $refreshCache = false;
741
-
742
-		$origValue = $value;
743
-		if ($sensitive || ($this->hasKey($app, $key, $lazy) && $this->isSensitive($app, $key, $lazy))) {
744
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
745
-		}
746
-
747
-		if ($this->hasKey($app, $key, $lazy)) {
748
-			/**
749
-			 * no update if key is already known with set lazy status and value is
750
-			 * not different, unless sensitivity is switched from false to true.
751
-			 */
752
-			if ($origValue === $this->getTypedValue($app, $key, $value, $lazy, $type)
753
-				&& (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
754
-				return false;
755
-			}
756
-		} else {
757
-			/**
758
-			 * if key is not known yet, we try to insert.
759
-			 * It might fail if the key exists with a different lazy flag.
760
-			 */
761
-			try {
762
-				$insert = $this->connection->getQueryBuilder();
763
-				$insert->insert('appconfig')
764
-					->setValue('appid', $insert->createNamedParameter($app))
765
-					->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
766
-					->setValue('type', $insert->createNamedParameter($type, IQueryBuilder::PARAM_INT))
767
-					->setValue('configkey', $insert->createNamedParameter($key))
768
-					->setValue('configvalue', $insert->createNamedParameter($value));
769
-				$insert->executeStatement();
770
-				$inserted = true;
771
-			} catch (DBException $e) {
772
-				if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
773
-					throw $e; // TODO: throw exception or just log and returns false !?
774
-				}
775
-			}
776
-		}
777
-
778
-		/**
779
-		 * We cannot insert a new row, meaning we need to update an already existing one
780
-		 */
781
-		if (!$inserted) {
782
-			$currType = $this->valueTypes[$app][$key] ?? 0;
783
-			if ($currType === 0) { // this might happen when switching lazy loading status
784
-				$this->loadConfigAll();
785
-				$currType = $this->valueTypes[$app][$key] ?? 0;
786
-			}
787
-
788
-			/**
789
-			 * This should only happen during the upgrade process from 28 to 29.
790
-			 * We only log a warning and set it to VALUE_MIXED.
791
-			 */
792
-			if ($currType === 0) {
793
-				$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]);
794
-				$currType = self::VALUE_MIXED;
795
-			}
796
-
797
-			/**
798
-			 * we only accept a different type from the one stored in database
799
-			 * if the one stored in database is not-defined (VALUE_MIXED)
800
-			 */
801
-			if (!$this->isTyped(self::VALUE_MIXED, $currType) &&
802
-				($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
803
-				try {
804
-					$currType = $this->convertTypeToString($currType);
805
-					$type = $this->convertTypeToString($type);
806
-				} catch (AppConfigIncorrectTypeException) {
807
-					// can be ignored, this was just needed for a better exception message.
808
-				}
809
-				throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
810
-			}
811
-
812
-			// we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
813
-			if ($sensitive || $this->isTyped(self::VALUE_SENSITIVE, $currType)) {
814
-				$type |= self::VALUE_SENSITIVE;
815
-			}
816
-
817
-			if ($lazy !== $this->isLazy($app, $key)) {
818
-				$refreshCache = true;
819
-			}
820
-
821
-			$update = $this->connection->getQueryBuilder();
822
-			$update->update('appconfig')
823
-				->set('configvalue', $update->createNamedParameter($value))
824
-				->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
825
-				->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
826
-				->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
827
-				->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
828
-
829
-			$update->executeStatement();
830
-		}
831
-
832
-		if ($refreshCache) {
833
-			$this->clearCache();
834
-			return true;
835
-		}
836
-
837
-		// update local cache
838
-		if ($lazy) {
839
-			$this->lazyCache[$app][$key] = $value;
840
-		} else {
841
-			$this->fastCache[$app][$key] = $value;
842
-		}
843
-		$this->valueTypes[$app][$key] = $type;
844
-
845
-		return true;
846
-	}
847
-
848
-	/**
849
-	 * Change the type of config value.
850
-	 *
851
-	 * **WARNING:** Method is internal and **MUST** not be used as it may break things.
852
-	 *
853
-	 * @param string $app id of the app
854
-	 * @param string $key config key
855
-	 * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
856
-	 *
857
-	 * @return bool TRUE if database update were necessary
858
-	 * @throws AppConfigUnknownKeyException if $key is now known in database
859
-	 * @throws AppConfigIncorrectTypeException if $type is not valid
860
-	 * @internal
861
-	 * @since 29.0.0
862
-	 */
863
-	public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
864
-		$this->assertParams($app, $key);
865
-		$this->loadConfigAll();
866
-		$lazy = $this->isLazy($app, $key);
867
-
868
-		// type can only be one type
869
-		if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
870
-			throw new AppConfigIncorrectTypeException('Unknown value type');
871
-		}
872
-
873
-		$currType = $this->valueTypes[$app][$key];
874
-		if (($type | self::VALUE_SENSITIVE) === ($currType | self::VALUE_SENSITIVE)) {
875
-			return false;
876
-		}
877
-
878
-		// we complete with sensitive flag if the stored value is set as sensitive
879
-		if ($this->isTyped(self::VALUE_SENSITIVE, $currType)) {
880
-			$type = $type | self::VALUE_SENSITIVE;
881
-		}
882
-
883
-		$update = $this->connection->getQueryBuilder();
884
-		$update->update('appconfig')
885
-			->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
886
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
887
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
888
-		$update->executeStatement();
889
-		$this->valueTypes[$app][$key] = $type;
890
-
891
-		return true;
892
-	}
893
-
894
-
895
-	/**
896
-	 * @inheritDoc
897
-	 *
898
-	 * @param string $app id of the app
899
-	 * @param string $key config key
900
-	 * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
901
-	 *
902
-	 * @return bool TRUE if entry was found in database and an update was necessary
903
-	 * @since 29.0.0
904
-	 */
905
-	public function updateSensitive(string $app, string $key, bool $sensitive): bool {
906
-		$this->assertParams($app, $key);
907
-		$this->loadConfigAll();
908
-
909
-		try {
910
-			if ($sensitive === $this->isSensitive($app, $key, null)) {
911
-				return false;
912
-			}
913
-		} catch (AppConfigUnknownKeyException $e) {
914
-			return false;
915
-		}
916
-
917
-		$lazy = $this->isLazy($app, $key);
918
-		if ($lazy) {
919
-			$cache = $this->lazyCache;
920
-		} else {
921
-			$cache = $this->fastCache;
922
-		}
923
-
924
-		if (!isset($cache[$app][$key])) {
925
-			throw new AppConfigUnknownKeyException('unknown config key');
926
-		}
927
-
928
-		/**
929
-		 * type returned by getValueType() is already cleaned from sensitive flag
930
-		 * we just need to update it based on $sensitive and store it in database
931
-		 */
932
-		$type = $this->getValueType($app, $key);
933
-		$value = $cache[$app][$key];
934
-		if ($sensitive) {
935
-			$type |= self::VALUE_SENSITIVE;
936
-			$value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
937
-		} else {
938
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
939
-		}
940
-
941
-		$update = $this->connection->getQueryBuilder();
942
-		$update->update('appconfig')
943
-			->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
944
-			->set('configvalue', $update->createNamedParameter($value))
945
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
946
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
947
-		$update->executeStatement();
948
-
949
-		$this->valueTypes[$app][$key] = $type;
950
-
951
-		return true;
952
-	}
953
-
954
-	/**
955
-	 * @inheritDoc
956
-	 *
957
-	 * @param string $app id of the app
958
-	 * @param string $key config key
959
-	 * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
960
-	 *
961
-	 * @return bool TRUE if entry was found in database and an update was necessary
962
-	 * @since 29.0.0
963
-	 */
964
-	public function updateLazy(string $app, string $key, bool $lazy): bool {
965
-		$this->assertParams($app, $key);
966
-		$this->loadConfigAll();
967
-
968
-		try {
969
-			if ($lazy === $this->isLazy($app, $key)) {
970
-				return false;
971
-			}
972
-		} catch (AppConfigUnknownKeyException $e) {
973
-			return false;
974
-		}
975
-
976
-		$update = $this->connection->getQueryBuilder();
977
-		$update->update('appconfig')
978
-			->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
979
-			->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
980
-			->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
981
-		$update->executeStatement();
982
-
983
-		// At this point, it is a lot safer to clean cache
984
-		$this->clearCache();
985
-
986
-		return true;
987
-	}
988
-
989
-	/**
990
-	 * @inheritDoc
991
-	 *
992
-	 * @param string $app id of the app
993
-	 * @param string $key config key
994
-	 *
995
-	 * @return array
996
-	 * @throws AppConfigUnknownKeyException if config key is not known in database
997
-	 * @since 29.0.0
998
-	 */
999
-	public function getDetails(string $app, string $key): array {
1000
-		$this->assertParams($app, $key);
1001
-		$this->loadConfigAll();
1002
-		$lazy = $this->isLazy($app, $key);
1003
-
1004
-		if ($lazy) {
1005
-			$cache = $this->lazyCache;
1006
-		} else {
1007
-			$cache = $this->fastCache;
1008
-		}
1009
-
1010
-		$type = $this->getValueType($app, $key);
1011
-		try {
1012
-			$typeString = $this->convertTypeToString($type);
1013
-		} catch (AppConfigIncorrectTypeException $e) {
1014
-			$this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
1015
-			$typeString = (string)$type;
1016
-		}
1017
-
1018
-		if (!isset($cache[$app][$key])) {
1019
-			throw new AppConfigUnknownKeyException('unknown config key');
1020
-		}
1021
-
1022
-		$value = $cache[$app][$key];
1023
-		$sensitive = $this->isSensitive($app, $key, null);
1024
-		if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
1025
-			$value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1026
-		}
1027
-
1028
-		return [
1029
-			'app' => $app,
1030
-			'key' => $key,
1031
-			'value' => $value,
1032
-			'type' => $type,
1033
-			'lazy' => $lazy,
1034
-			'typeString' => $typeString,
1035
-			'sensitive' => $sensitive
1036
-		];
1037
-	}
1038
-
1039
-	/**
1040
-	 * @param string $type
1041
-	 *
1042
-	 * @return int
1043
-	 * @throws AppConfigIncorrectTypeException
1044
-	 * @since 29.0.0
1045
-	 */
1046
-	public function convertTypeToInt(string $type): int {
1047
-		return match (strtolower($type)) {
1048
-			'mixed' => IAppConfig::VALUE_MIXED,
1049
-			'string' => IAppConfig::VALUE_STRING,
1050
-			'integer' => IAppConfig::VALUE_INT,
1051
-			'float' => IAppConfig::VALUE_FLOAT,
1052
-			'boolean' => IAppConfig::VALUE_BOOL,
1053
-			'array' => IAppConfig::VALUE_ARRAY,
1054
-			default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
1055
-		};
1056
-	}
1057
-
1058
-	/**
1059
-	 * @param int $type
1060
-	 *
1061
-	 * @return string
1062
-	 * @throws AppConfigIncorrectTypeException
1063
-	 * @since 29.0.0
1064
-	 */
1065
-	public function convertTypeToString(int $type): string {
1066
-		$type &= ~self::VALUE_SENSITIVE;
1067
-
1068
-		return match ($type) {
1069
-			IAppConfig::VALUE_MIXED => 'mixed',
1070
-			IAppConfig::VALUE_STRING => 'string',
1071
-			IAppConfig::VALUE_INT => 'integer',
1072
-			IAppConfig::VALUE_FLOAT => 'float',
1073
-			IAppConfig::VALUE_BOOL => 'boolean',
1074
-			IAppConfig::VALUE_ARRAY => 'array',
1075
-			default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
1076
-		};
1077
-	}
1078
-
1079
-	/**
1080
-	 * @inheritDoc
1081
-	 *
1082
-	 * @param string $app id of the app
1083
-	 * @param string $key config key
1084
-	 *
1085
-	 * @since 29.0.0
1086
-	 */
1087
-	public function deleteKey(string $app, string $key): void {
1088
-		$this->assertParams($app, $key);
1089
-		$qb = $this->connection->getQueryBuilder();
1090
-		$qb->delete('appconfig')
1091
-			->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1092
-			->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1093
-		$qb->executeStatement();
1094
-
1095
-		unset($this->lazyCache[$app][$key]);
1096
-		unset($this->fastCache[$app][$key]);
1097
-		unset($this->valueTypes[$app][$key]);
1098
-	}
1099
-
1100
-	/**
1101
-	 * @inheritDoc
1102
-	 *
1103
-	 * @param string $app id of the app
1104
-	 *
1105
-	 * @since 29.0.0
1106
-	 */
1107
-	public function deleteApp(string $app): void {
1108
-		$this->assertParams($app);
1109
-		$qb = $this->connection->getQueryBuilder();
1110
-		$qb->delete('appconfig')
1111
-			->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
1112
-		$qb->executeStatement();
1113
-
1114
-		$this->clearCache();
1115
-	}
1116
-
1117
-	/**
1118
-	 * @inheritDoc
1119
-	 *
1120
-	 * @param bool $reload set to TRUE to refill cache instantly after clearing it
1121
-	 *
1122
-	 * @since 29.0.0
1123
-	 */
1124
-	public function clearCache(bool $reload = false): void {
1125
-		$this->lazyLoaded = $this->fastLoaded = false;
1126
-		$this->lazyCache = $this->fastCache = $this->valueTypes = [];
1127
-
1128
-		if (!$reload) {
1129
-			return;
1130
-		}
1131
-
1132
-		$this->loadConfigAll();
1133
-	}
1134
-
1135
-
1136
-	/**
1137
-	 * For debug purpose.
1138
-	 * Returns the cached data.
1139
-	 *
1140
-	 * @return array
1141
-	 * @since 29.0.0
1142
-	 * @internal
1143
-	 */
1144
-	public function statusCache(): array {
1145
-		return [
1146
-			'fastLoaded' => $this->fastLoaded,
1147
-			'fastCache' => $this->fastCache,
1148
-			'lazyLoaded' => $this->lazyLoaded,
1149
-			'lazyCache' => $this->lazyCache,
1150
-		];
1151
-	}
1152
-
1153
-	/**
1154
-	 * @param int $needle bitflag to search
1155
-	 * @param int $type known value
1156
-	 *
1157
-	 * @return bool TRUE if bitflag $needle is set in $type
1158
-	 */
1159
-	private function isTyped(int $needle, int $type): bool {
1160
-		return (($needle & $type) !== 0);
1161
-	}
1162
-
1163
-	/**
1164
-	 * Confirm the string set for app and key fit the database description
1165
-	 *
1166
-	 * @param string $app assert $app fit in database
1167
-	 * @param string $configKey assert config key fit in database
1168
-	 * @param bool $allowEmptyApp $app can be empty string
1169
-	 * @param int $valueType assert value type is only one type
1170
-	 *
1171
-	 * @throws InvalidArgumentException
1172
-	 */
1173
-	private function assertParams(string $app = '', string $configKey = '', bool $allowEmptyApp = false, int $valueType = -1): void {
1174
-		if (!$allowEmptyApp && $app === '') {
1175
-			throw new InvalidArgumentException('app cannot be an empty string');
1176
-		}
1177
-		if (strlen($app) > self::APP_MAX_LENGTH) {
1178
-			throw new InvalidArgumentException(
1179
-				'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
1180
-			);
1181
-		}
1182
-		if (strlen($configKey) > self::KEY_MAX_LENGTH) {
1183
-			throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
1184
-		}
1185
-		if ($valueType > -1) {
1186
-			$valueType &= ~self::VALUE_SENSITIVE;
1187
-			if (!in_array($valueType, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
1188
-				throw new InvalidArgumentException('Unknown value type');
1189
-			}
1190
-		}
1191
-	}
1192
-
1193
-	private function loadConfigAll(?string $app = null): void {
1194
-		$this->loadConfig($app, null);
1195
-	}
1196
-
1197
-	/**
1198
-	 * Load normal config or config set as lazy loaded
1199
-	 *
1200
-	 * @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
1201
-	 */
1202
-	private function loadConfig(?string $app = null, ?bool $lazy = false): void {
1203
-		if ($this->isLoaded($lazy)) {
1204
-			return;
1205
-		}
1206
-
1207
-		// if lazy is null or true, we debug log
1208
-		if (($lazy ?? true) !== false && $app !== null) {
1209
-			$exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "' . $app . '"');
1210
-			$this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
1211
-		}
1212
-
1213
-		$qb = $this->connection->getQueryBuilder();
1214
-		$qb->from('appconfig');
1215
-
1216
-		// we only need value from lazy when loadConfig does not specify it
1217
-		$qb->select('appid', 'configkey', 'configvalue', 'type');
1218
-
1219
-		if ($lazy !== null) {
1220
-			$qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
1221
-		} else {
1222
-			$qb->addSelect('lazy');
1223
-		}
1224
-
1225
-		$result = $qb->executeQuery();
1226
-		$rows = $result->fetchAll();
1227
-		foreach ($rows as $row) {
1228
-			// most of the time, 'lazy' is not in the select because its value is already known
1229
-			if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
1230
-				$this->lazyCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1231
-			} else {
1232
-				$this->fastCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1233
-			}
1234
-			$this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
1235
-		}
1236
-		$result->closeCursor();
1237
-		$this->setAsLoaded($lazy);
1238
-	}
1239
-
1240
-	/**
1241
-	 * if $lazy is:
1242
-	 *  - false: will returns true if fast config is loaded
1243
-	 *  - true : will returns true if lazy config is loaded
1244
-	 *  - null : will returns true if both config are loaded
1245
-	 *
1246
-	 * @param bool $lazy
1247
-	 *
1248
-	 * @return bool
1249
-	 */
1250
-	private function isLoaded(?bool $lazy): bool {
1251
-		if ($lazy === null) {
1252
-			return $this->lazyLoaded && $this->fastLoaded;
1253
-		}
1254
-
1255
-		return $lazy ? $this->lazyLoaded : $this->fastLoaded;
1256
-	}
1257
-
1258
-	/**
1259
-	 * if $lazy is:
1260
-	 * - false: set fast config as loaded
1261
-	 * - true : set lazy config as loaded
1262
-	 * - null : set both config as loaded
1263
-	 *
1264
-	 * @param bool $lazy
1265
-	 */
1266
-	private function setAsLoaded(?bool $lazy): void {
1267
-		if ($lazy === null) {
1268
-			$this->fastLoaded = true;
1269
-			$this->lazyLoaded = true;
1270
-
1271
-			return;
1272
-		}
1273
-
1274
-		if ($lazy) {
1275
-			$this->lazyLoaded = true;
1276
-		} else {
1277
-			$this->fastLoaded = true;
1278
-		}
1279
-	}
1280
-
1281
-	/**
1282
-	 * Gets the config value
1283
-	 *
1284
-	 * @param string $app app
1285
-	 * @param string $key key
1286
-	 * @param string $default = null, default value if the key does not exist
1287
-	 *
1288
-	 * @return string the value or $default
1289
-	 * @deprecated 29.0.0 use getValue*()
1290
-	 *
1291
-	 * This function gets a value from the appconfig table. If the key does
1292
-	 * not exist the default value will be returned
1293
-	 */
1294
-	public function getValue($app, $key, $default = null) {
1295
-		$this->loadConfig($app);
1296
-
1297
-		return $this->fastCache[$app][$key] ?? $default;
1298
-	}
1299
-
1300
-	/**
1301
-	 * Sets a value. If the key did not exist before it will be created.
1302
-	 *
1303
-	 * @param string $app app
1304
-	 * @param string $key key
1305
-	 * @param string|float|int $value value
1306
-	 *
1307
-	 * @return bool True if the value was inserted or updated, false if the value was the same
1308
-	 * @throws AppConfigTypeConflictException
1309
-	 * @throws AppConfigUnknownKeyException
1310
-	 * @deprecated 29.0.0
1311
-	 */
1312
-	public function setValue($app, $key, $value) {
1313
-		/**
1314
-		 * TODO: would it be overkill, or decently improve performance, to catch
1315
-		 * call to this method with $key='enabled' and 'hide' config value related
1316
-		 * to $app when the app is disabled (by modifying entry in database: lazy=lazy+2)
1317
-		 * or enabled (lazy=lazy-2)
1318
-		 *
1319
-		 * this solution would remove the loading of config values from disabled app
1320
-		 * unless calling the method {@see loadConfigAll()}
1321
-		 */
1322
-		return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
1323
-	}
1324
-
1325
-
1326
-	/**
1327
-	 * get multiple values, either the app or key can be used as wildcard by setting it to false
1328
-	 *
1329
-	 * @param string|false $app
1330
-	 * @param string|false $key
1331
-	 *
1332
-	 * @return array|false
1333
-	 * @deprecated 29.0.0 use {@see getAllValues()}
1334
-	 */
1335
-	public function getValues($app, $key) {
1336
-		if (($app !== false) === ($key !== false)) {
1337
-			return false;
1338
-		}
1339
-
1340
-		$key = ($key === false) ? '' : $key;
1341
-		if (!$app) {
1342
-			return $this->searchValues($key, false, self::VALUE_MIXED);
1343
-		} else {
1344
-			return $this->getAllValues($app, $key);
1345
-		}
1346
-	}
1347
-
1348
-	/**
1349
-	 * get all values of the app or and filters out sensitive data
1350
-	 *
1351
-	 * @param string $app
1352
-	 *
1353
-	 * @return array
1354
-	 * @deprecated 29.0.0 use {@see getAllValues()}
1355
-	 */
1356
-	public function getFilteredValues($app) {
1357
-		return $this->getAllValues($app, filtered: true);
1358
-	}
1359
-
1360
-
1361
-	/**
1362
-	 * **Warning:** avoid default NULL value for $lazy as this will
1363
-	 * load all lazy values from the database
1364
-	 *
1365
-	 * @param string $app
1366
-	 * @param array<string, string> $values ['key' => 'value']
1367
-	 * @param bool|null $lazy
1368
-	 *
1369
-	 * @return array<string, string|int|float|bool|array>
1370
-	 */
1371
-	private function formatAppValues(string $app, array $values, ?bool $lazy = null): array {
1372
-		foreach ($values as $key => $value) {
1373
-			try {
1374
-				$type = $this->getValueType($app, $key, $lazy);
1375
-			} catch (AppConfigUnknownKeyException $e) {
1376
-				continue;
1377
-			}
1378
-
1379
-			$values[$key] = $this->convertTypedValue($value, $type);
1380
-		}
1381
-
1382
-		return $values;
1383
-	}
1384
-
1385
-	/**
1386
-	 * convert string value to the expected type
1387
-	 *
1388
-	 * @param string $value
1389
-	 * @param int $type
1390
-	 *
1391
-	 * @return string|int|float|bool|array
1392
-	 */
1393
-	private function convertTypedValue(string $value, int $type): string|int|float|bool|array {
1394
-		switch ($type) {
1395
-			case self::VALUE_INT:
1396
-				return (int)$value;
1397
-			case self::VALUE_FLOAT:
1398
-				return (float)$value;
1399
-			case self::VALUE_BOOL:
1400
-				return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
1401
-			case self::VALUE_ARRAY:
1402
-				try {
1403
-					return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
1404
-				} catch (JsonException $e) {
1405
-					// ignoreable
1406
-				}
1407
-				break;
1408
-		}
1409
-		return $value;
1410
-	}
1411
-
1412
-	/**
1413
-	 * @param string $app
1414
-	 *
1415
-	 * @return string[]
1416
-	 * @deprecated 29.0.0 data sensitivity should be set when calling setValue*()
1417
-	 */
1418
-	private function getSensitiveKeys(string $app): array {
1419
-		$sensitiveValues = [
1420
-			'circles' => [
1421
-				'/^key_pairs$/',
1422
-				'/^local_gskey$/',
1423
-			],
1424
-			'call_summary_bot' => [
1425
-				'/^secret_(.*)$/',
1426
-			],
1427
-			'external' => [
1428
-				'/^sites$/',
1429
-				'/^jwt_token_privkey_(.*)$/',
1430
-			],
1431
-			'globalsiteselector' => [
1432
-				'/^gss\.jwt\.key$/',
1433
-			],
1434
-			'gpgmailer' => [
1435
-				'/^GpgServerKey$/',
1436
-			],
1437
-			'integration_discourse' => [
1438
-				'/^private_key$/',
1439
-				'/^public_key$/',
1440
-			],
1441
-			'integration_dropbox' => [
1442
-				'/^client_id$/',
1443
-				'/^client_secret$/',
1444
-			],
1445
-			'integration_github' => [
1446
-				'/^client_id$/',
1447
-				'/^client_secret$/',
1448
-			],
1449
-			'integration_gitlab' => [
1450
-				'/^client_id$/',
1451
-				'/^client_secret$/',
1452
-				'/^oauth_instance_url$/',
1453
-			],
1454
-			'integration_google' => [
1455
-				'/^client_id$/',
1456
-				'/^client_secret$/',
1457
-			],
1458
-			'integration_jira' => [
1459
-				'/^client_id$/',
1460
-				'/^client_secret$/',
1461
-				'/^forced_instance_url$/',
1462
-			],
1463
-			'integration_onedrive' => [
1464
-				'/^client_id$/',
1465
-				'/^client_secret$/',
1466
-			],
1467
-			'integration_openproject' => [
1468
-				'/^client_id$/',
1469
-				'/^client_secret$/',
1470
-				'/^oauth_instance_url$/',
1471
-			],
1472
-			'integration_reddit' => [
1473
-				'/^client_id$/',
1474
-				'/^client_secret$/',
1475
-			],
1476
-			'integration_suitecrm' => [
1477
-				'/^client_id$/',
1478
-				'/^client_secret$/',
1479
-				'/^oauth_instance_url$/',
1480
-			],
1481
-			'integration_twitter' => [
1482
-				'/^consumer_key$/',
1483
-				'/^consumer_secret$/',
1484
-				'/^followed_user$/',
1485
-			],
1486
-			'integration_zammad' => [
1487
-				'/^client_id$/',
1488
-				'/^client_secret$/',
1489
-				'/^oauth_instance_url$/',
1490
-			],
1491
-			'maps' => [
1492
-				'/^mapboxAPIKEY$/',
1493
-			],
1494
-			'notify_push' => [
1495
-				'/^cookie$/',
1496
-			],
1497
-			'onlyoffice' => [
1498
-				'/^jwt_secret$/',
1499
-			],
1500
-			'passwords' => [
1501
-				'/^SSEv1ServerKey$/',
1502
-			],
1503
-			'serverinfo' => [
1504
-				'/^token$/',
1505
-			],
1506
-			'spreed' => [
1507
-				'/^bridge_bot_password$/',
1508
-				'/^hosted-signaling-server-(.*)$/',
1509
-				'/^recording_servers$/',
1510
-				'/^signaling_servers$/',
1511
-				'/^signaling_ticket_secret$/',
1512
-				'/^signaling_token_privkey_(.*)$/',
1513
-				'/^signaling_token_pubkey_(.*)$/',
1514
-				'/^sip_bridge_dialin_info$/',
1515
-				'/^sip_bridge_shared_secret$/',
1516
-				'/^stun_servers$/',
1517
-				'/^turn_servers$/',
1518
-				'/^turn_server_secret$/',
1519
-			],
1520
-			'support' => [
1521
-				'/^last_response$/',
1522
-				'/^potential_subscription_key$/',
1523
-				'/^subscription_key$/',
1524
-			],
1525
-			'theming' => [
1526
-				'/^imprintUrl$/',
1527
-				'/^privacyUrl$/',
1528
-				'/^slogan$/',
1529
-				'/^url$/',
1530
-			],
1531
-			'twofactor_gateway' => [
1532
-				'/^.*token$/',
1533
-			],
1534
-			'user_ldap' => [
1535
-				'/^(s..)?ldap_agent_password$/',
1536
-			],
1537
-			'user_saml' => [
1538
-				'/^idp-x509cert$/',
1539
-			],
1540
-			'whiteboard' => [
1541
-				'/^jwt_secret_key$/',
1542
-			],
1543
-		];
1544
-
1545
-		return $sensitiveValues[$app] ?? [];
1546
-	}
1547
-
1548
-	/**
1549
-	 * Clear all the cached app config values
1550
-	 * New cache will be generated next time a config value is retrieved
1551
-	 *
1552
-	 * @deprecated 29.0.0 use {@see clearCache()}
1553
-	 */
1554
-	public function clearCachedConfig(): void {
1555
-		$this->clearCache();
1556
-	}
1557
-
1558
-	/**
1559
-	 * match and apply current use of config values with defined lexicon
1560
-	 *
1561
-	 * @throws AppConfigUnknownKeyException
1562
-	 * @throws AppConfigTypeConflictException
1563
-	 * @return bool TRUE if everything is fine compared to lexicon or lexicon does not exist
1564
-	 */
1565
-	private function matchAndApplyLexiconDefinition(
1566
-		string $app,
1567
-		string $key,
1568
-		bool &$lazy,
1569
-		int &$type,
1570
-		string &$default = '',
1571
-	): bool {
1572
-		if (in_array($key,
1573
-			[
1574
-				'enabled',
1575
-				'installed_version',
1576
-				'types',
1577
-			])) {
1578
-			return true; // we don't break stuff for this list of config keys.
1579
-		}
1580
-		$configDetails = $this->getConfigDetailsFromLexicon($app);
1581
-		if (!array_key_exists($key, $configDetails['entries'])) {
1582
-			return $this->applyLexiconStrictness(
1583
-				$configDetails['strictness'],
1584
-				'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon'
1585
-			);
1586
-		}
1587
-
1588
-		/** @var ConfigLexiconEntry $configValue */
1589
-		$configValue = $configDetails['entries'][$key];
1590
-		$type &= ~self::VALUE_SENSITIVE;
1591
-
1592
-		$appConfigValueType = $configValue->getValueType()->toAppConfigFlag();
1593
-		if ($type === self::VALUE_MIXED) {
1594
-			$type = $appConfigValueType; // we overwrite if value was requested as mixed
1595
-		} elseif ($appConfigValueType !== $type) {
1596
-			throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1597
-		}
1598
-
1599
-		$lazy = $configValue->isLazy();
1600
-		$default = $configValue->getDefault() ?? $default; // default from Lexicon got priority
1601
-		if ($configValue->isFlagged(self::FLAG_SENSITIVE)) {
1602
-			$type |= self::VALUE_SENSITIVE;
1603
-		}
1604
-		if ($configValue->isDeprecated()) {
1605
-			$this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
1606
-		}
1607
-
1608
-		return true;
1609
-	}
1610
-
1611
-	/**
1612
-	 * manage ConfigLexicon behavior based on strictness set in IConfigLexicon
1613
-	 *
1614
-	 * @param ConfigLexiconStrictness|null $strictness
1615
-	 * @param string $line
1616
-	 *
1617
-	 * @return bool TRUE if conflict can be fully ignored, FALSE if action should be not performed
1618
-	 * @throws AppConfigUnknownKeyException if strictness implies exception
1619
-	 * @see IConfigLexicon::getStrictness()
1620
-	 */
1621
-	private function applyLexiconStrictness(
1622
-		?ConfigLexiconStrictness $strictness,
1623
-		string $line = '',
1624
-	): bool {
1625
-		if ($strictness === null) {
1626
-			return true;
1627
-		}
1628
-
1629
-		switch ($strictness) {
1630
-			case ConfigLexiconStrictness::IGNORE:
1631
-				return true;
1632
-			case ConfigLexiconStrictness::NOTICE:
1633
-				$this->logger->notice($line);
1634
-				return true;
1635
-			case ConfigLexiconStrictness::WARNING:
1636
-				$this->logger->warning($line);
1637
-				return false;
1638
-		}
1639
-
1640
-		throw new AppConfigUnknownKeyException($line);
1641
-	}
1642
-
1643
-	/**
1644
-	 * extract details from registered $appId's config lexicon
1645
-	 *
1646
-	 * @param string $appId
1647
-	 *
1648
-	 * @return array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}
1649
-	 */
1650
-	private function getConfigDetailsFromLexicon(string $appId): array {
1651
-		if (!array_key_exists($appId, $this->configLexiconDetails)) {
1652
-			$entries = [];
1653
-			$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
1654
-			$configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
1655
-			foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
1656
-				$entries[$configEntry->getKey()] = $configEntry;
1657
-			}
1658
-
1659
-			$this->configLexiconDetails[$appId] = [
1660
-				'entries' => $entries,
1661
-				'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE
1662
-			];
1663
-		}
1664
-
1665
-		return $this->configLexiconDetails[$appId];
1666
-	}
1667
-
1668
-	/**
1669
-	 * Returns the installed versions of all apps
1670
-	 *
1671
-	 * @return array<string, string>
1672
-	 */
1673
-	public function getAppInstalledVersions(bool $onlyEnabled = false): array {
1674
-		if ($this->appVersionsCache === null) {
1675
-			/** @var array<string, string> */
1676
-			$this->appVersionsCache = $this->searchValues('installed_version', false, IAppConfig::VALUE_STRING);
1677
-		}
1678
-		if ($onlyEnabled) {
1679
-			return array_filter(
1680
-				$this->appVersionsCache,
1681
-				fn (string $app): bool => $this->getValueString($app, 'enabled', 'no') !== 'no',
1682
-				ARRAY_FILTER_USE_KEY
1683
-			);
1684
-		}
1685
-		return $this->appVersionsCache;
1686
-	}
49
+    private const APP_MAX_LENGTH = 32;
50
+    private const KEY_MAX_LENGTH = 64;
51
+    private const ENCRYPTION_PREFIX = '$AppConfigEncryption$';
52
+    private const ENCRYPTION_PREFIX_LENGTH = 21; // strlen(self::ENCRYPTION_PREFIX)
53
+
54
+    /** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
55
+    private array $fastCache = [];   // cache for normal config keys
56
+    /** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
57
+    private array $lazyCache = [];   // cache for lazy config keys
58
+    /** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
59
+    private array $valueTypes = [];  // type for all config values
60
+    private bool $fastLoaded = false;
61
+    private bool $lazyLoaded = false;
62
+    /** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */
63
+    private array $configLexiconDetails = [];
64
+
65
+    /** @var ?array<string, string> */
66
+    private ?array $appVersionsCache = null;
67
+
68
+    public function __construct(
69
+        protected IDBConnection $connection,
70
+        protected LoggerInterface $logger,
71
+        protected ICrypto $crypto,
72
+    ) {
73
+    }
74
+
75
+    /**
76
+     * @inheritDoc
77
+     *
78
+     * @return list<string> list of app ids
79
+     * @since 7.0.0
80
+     */
81
+    public function getApps(): array {
82
+        $this->loadConfigAll();
83
+        $apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
84
+        sort($apps);
85
+
86
+        return array_values(array_unique($apps));
87
+    }
88
+
89
+    /**
90
+     * @inheritDoc
91
+     *
92
+     * @param string $app id of the app
93
+     *
94
+     * @return list<string> list of stored config keys
95
+     * @since 29.0.0
96
+     */
97
+    public function getKeys(string $app): array {
98
+        $this->assertParams($app);
99
+        $this->loadConfigAll($app);
100
+        $keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
101
+        sort($keys);
102
+
103
+        return array_values(array_unique($keys));
104
+    }
105
+
106
+    /**
107
+     * @inheritDoc
108
+     *
109
+     * @param string $app id of the app
110
+     * @param string $key config key
111
+     * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
112
+     *
113
+     * @return bool TRUE if key exists
114
+     * @since 7.0.0
115
+     * @since 29.0.0 Added the $lazy argument
116
+     */
117
+    public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
118
+        $this->assertParams($app, $key);
119
+        $this->loadConfig($app, $lazy);
120
+
121
+        if ($lazy === null) {
122
+            $appCache = $this->getAllValues($app);
123
+            return isset($appCache[$key]);
124
+        }
125
+
126
+        if ($lazy) {
127
+            return isset($this->lazyCache[$app][$key]);
128
+        }
129
+
130
+        return isset($this->fastCache[$app][$key]);
131
+    }
132
+
133
+    /**
134
+     * @param string $app id of the app
135
+     * @param string $key config key
136
+     * @param bool|null $lazy TRUE to search within lazy loaded config, NULL to search within all config
137
+     *
138
+     * @return bool
139
+     * @throws AppConfigUnknownKeyException if config key is not known
140
+     * @since 29.0.0
141
+     */
142
+    public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
143
+        $this->assertParams($app, $key);
144
+        $this->loadConfig(null, $lazy);
145
+
146
+        if (!isset($this->valueTypes[$app][$key])) {
147
+            throw new AppConfigUnknownKeyException('unknown config key');
148
+        }
149
+
150
+        return $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key]);
151
+    }
152
+
153
+    /**
154
+     * @inheritDoc
155
+     *
156
+     * @param string $app if of the app
157
+     * @param string $key config key
158
+     *
159
+     * @return bool TRUE if config is lazy loaded
160
+     * @throws AppConfigUnknownKeyException if config key is not known
161
+     * @see IAppConfig for details about lazy loading
162
+     * @since 29.0.0
163
+     */
164
+    public function isLazy(string $app, string $key): bool {
165
+        // there is a huge probability the non-lazy config are already loaded
166
+        if ($this->hasKey($app, $key, false)) {
167
+            return false;
168
+        }
169
+
170
+        // key not found, we search in the lazy config
171
+        if ($this->hasKey($app, $key, true)) {
172
+            return true;
173
+        }
174
+
175
+        throw new AppConfigUnknownKeyException('unknown config key');
176
+    }
177
+
178
+
179
+    /**
180
+     * @inheritDoc
181
+     *
182
+     * @param string $app id of the app
183
+     * @param string $prefix config keys prefix to search
184
+     * @param bool $filtered TRUE to hide sensitive config values. Value are replaced by {@see IConfig::SENSITIVE_VALUE}
185
+     *
186
+     * @return array<string, string|int|float|bool|array> [configKey => configValue]
187
+     * @since 29.0.0
188
+     */
189
+    public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
190
+        $this->assertParams($app, $prefix);
191
+        // if we want to filter values, we need to get sensitivity
192
+        $this->loadConfigAll($app);
193
+        // array_merge() will remove numeric keys (here config keys), so addition arrays instead
194
+        $values = $this->formatAppValues($app, ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []));
195
+        $values = array_filter(
196
+            $values,
197
+            function (string $key) use ($prefix): bool {
198
+                return str_starts_with($key, $prefix); // filter values based on $prefix
199
+            }, ARRAY_FILTER_USE_KEY
200
+        );
201
+
202
+        if (!$filtered) {
203
+            return $values;
204
+        }
205
+
206
+        /**
207
+         * Using the old (deprecated) list of sensitive values.
208
+         */
209
+        foreach ($this->getSensitiveKeys($app) as $sensitiveKeyExp) {
210
+            $sensitiveKeys = preg_grep($sensitiveKeyExp, array_keys($values));
211
+            foreach ($sensitiveKeys as $sensitiveKey) {
212
+                $this->valueTypes[$app][$sensitiveKey] = ($this->valueTypes[$app][$sensitiveKey] ?? 0) | self::VALUE_SENSITIVE;
213
+            }
214
+        }
215
+
216
+        $result = [];
217
+        foreach ($values as $key => $value) {
218
+            $result[$key] = $this->isTyped(self::VALUE_SENSITIVE, $this->valueTypes[$app][$key] ?? 0) ? IConfig::SENSITIVE_VALUE : $value;
219
+        }
220
+
221
+        return $result;
222
+    }
223
+
224
+    /**
225
+     * @inheritDoc
226
+     *
227
+     * @param string $key config key
228
+     * @param bool $lazy search within lazy loaded config
229
+     * @param int|null $typedAs enforce type for the returned values ({@see self::VALUE_STRING} and others)
230
+     *
231
+     * @return array<string, string|int|float|bool|array> [appId => configValue]
232
+     * @since 29.0.0
233
+     */
234
+    public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array {
235
+        $this->assertParams('', $key, true);
236
+        $this->loadConfig(null, $lazy);
237
+
238
+        /** @var array<array-key, array<array-key, mixed>> $cache */
239
+        if ($lazy) {
240
+            $cache = $this->lazyCache;
241
+        } else {
242
+            $cache = $this->fastCache;
243
+        }
244
+
245
+        $values = [];
246
+        foreach (array_keys($cache) as $app) {
247
+            if (isset($cache[$app][$key])) {
248
+                $values[$app] = $this->convertTypedValue($cache[$app][$key], $typedAs ?? $this->getValueType((string)$app, $key, $lazy));
249
+            }
250
+        }
251
+
252
+        return $values;
253
+    }
254
+
255
+
256
+    /**
257
+     * Get the config value as string.
258
+     * If the value does not exist the given default will be returned.
259
+     *
260
+     * Set lazy to `null` to ignore it and get the value from either source.
261
+     *
262
+     * **WARNING:** Method is internal and **SHOULD** not be used, as it is better to get the value with a type.
263
+     *
264
+     * @param string $app id of the app
265
+     * @param string $key config key
266
+     * @param string $default config value
267
+     * @param null|bool $lazy get config as lazy loaded or not. can be NULL
268
+     *
269
+     * @return string the value or $default
270
+     * @internal
271
+     * @since 29.0.0
272
+     * @see IAppConfig for explanation about lazy loading
273
+     * @see getValueString()
274
+     * @see getValueInt()
275
+     * @see getValueFloat()
276
+     * @see getValueBool()
277
+     * @see getValueArray()
278
+     */
279
+    public function getValueMixed(
280
+        string $app,
281
+        string $key,
282
+        string $default = '',
283
+        ?bool $lazy = false,
284
+    ): string {
285
+        try {
286
+            $lazy = ($lazy === null) ? $this->isLazy($app, $key) : $lazy;
287
+        } catch (AppConfigUnknownKeyException $e) {
288
+            return $default;
289
+        }
290
+
291
+        return $this->getTypedValue(
292
+            $app,
293
+            $key,
294
+            $default,
295
+            $lazy,
296
+            self::VALUE_MIXED
297
+        );
298
+    }
299
+
300
+    /**
301
+     * @inheritDoc
302
+     *
303
+     * @param string $app id of the app
304
+     * @param string $key config key
305
+     * @param string $default default value
306
+     * @param bool $lazy search within lazy loaded config
307
+     *
308
+     * @return string stored config value or $default if not set in database
309
+     * @throws InvalidArgumentException if one of the argument format is invalid
310
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
311
+     * @since 29.0.0
312
+     * @see IAppConfig for explanation about lazy loading
313
+     */
314
+    public function getValueString(
315
+        string $app,
316
+        string $key,
317
+        string $default = '',
318
+        bool $lazy = false,
319
+    ): string {
320
+        return $this->getTypedValue($app, $key, $default, $lazy, self::VALUE_STRING);
321
+    }
322
+
323
+    /**
324
+     * @inheritDoc
325
+     *
326
+     * @param string $app id of the app
327
+     * @param string $key config key
328
+     * @param int $default default value
329
+     * @param bool $lazy search within lazy loaded config
330
+     *
331
+     * @return int stored config value or $default if not set in database
332
+     * @throws InvalidArgumentException if one of the argument format is invalid
333
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
334
+     * @since 29.0.0
335
+     * @see IAppConfig for explanation about lazy loading
336
+     */
337
+    public function getValueInt(
338
+        string $app,
339
+        string $key,
340
+        int $default = 0,
341
+        bool $lazy = false,
342
+    ): int {
343
+        return (int)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_INT);
344
+    }
345
+
346
+    /**
347
+     * @inheritDoc
348
+     *
349
+     * @param string $app id of the app
350
+     * @param string $key config key
351
+     * @param float $default default value
352
+     * @param bool $lazy search within lazy loaded config
353
+     *
354
+     * @return float stored config value or $default if not set in database
355
+     * @throws InvalidArgumentException if one of the argument format is invalid
356
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
357
+     * @since 29.0.0
358
+     * @see IAppConfig for explanation about lazy loading
359
+     */
360
+    public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
361
+        return (float)$this->getTypedValue($app, $key, (string)$default, $lazy, self::VALUE_FLOAT);
362
+    }
363
+
364
+    /**
365
+     * @inheritDoc
366
+     *
367
+     * @param string $app id of the app
368
+     * @param string $key config key
369
+     * @param bool $default default value
370
+     * @param bool $lazy search within lazy loaded config
371
+     *
372
+     * @return bool stored config value or $default if not set in database
373
+     * @throws InvalidArgumentException if one of the argument format is invalid
374
+     * @throws AppConfigTypeConflictException in case of conflict with the value type set in database
375
+     * @since 29.0.0
376
+     * @see IAppConfig for explanation about lazy loading
377
+     */
378
+    public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
379
+        $b = strtolower($this->getTypedValue($app, $key, $default ? 'true' : 'false', $lazy, self::VALUE_BOOL));
380
+        return in_array($b, ['1', 'true', 'yes', 'on']);
381
+    }
382
+
383
+    /**
384
+     * @inheritDoc
385
+     *
386
+     * @param string $app id of the app
387
+     * @param string $key config key
388
+     * @param array $default default value
389
+     * @param bool $lazy search within lazy loaded config
390
+     *
391
+     * @return array 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 getValueArray(
398
+        string $app,
399
+        string $key,
400
+        array $default = [],
401
+        bool $lazy = false,
402
+    ): array {
403
+        try {
404
+            $defaultJson = json_encode($default, JSON_THROW_ON_ERROR);
405
+            $value = json_decode($this->getTypedValue($app, $key, $defaultJson, $lazy, self::VALUE_ARRAY), true, flags: JSON_THROW_ON_ERROR);
406
+
407
+            return is_array($value) ? $value : [];
408
+        } catch (JsonException) {
409
+            return [];
410
+        }
411
+    }
412
+
413
+    /**
414
+     * @param string $app id of the app
415
+     * @param string $key config key
416
+     * @param string $default default value
417
+     * @param bool $lazy search within lazy loaded config
418
+     * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT}{@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
419
+     *
420
+     * @return string
421
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
422
+     * @throws InvalidArgumentException
423
+     */
424
+    private function getTypedValue(
425
+        string $app,
426
+        string $key,
427
+        string $default,
428
+        bool $lazy,
429
+        int $type,
430
+    ): string {
431
+        $this->assertParams($app, $key, valueType: $type);
432
+        if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default)) {
433
+            return $default; // returns default if strictness of lexicon is set to WARNING (block and report)
434
+        }
435
+        $this->loadConfig($app, $lazy);
436
+
437
+        /**
438
+         * We ignore check if mixed type is requested.
439
+         * If type of stored value is set as mixed, we don't filter.
440
+         * If type of stored value is defined, we compare with the one requested.
441
+         */
442
+        $knownType = $this->valueTypes[$app][$key] ?? 0;
443
+        if (!$this->isTyped(self::VALUE_MIXED, $type)
444
+            && $knownType > 0
445
+            && !$this->isTyped(self::VALUE_MIXED, $knownType)
446
+            && !$this->isTyped($type, $knownType)) {
447
+            $this->logger->warning('conflict with value type from database', ['app' => $app, 'key' => $key, 'type' => $type, 'knownType' => $knownType]);
448
+            throw new AppConfigTypeConflictException('conflict with value type from database');
449
+        }
450
+
451
+        /**
452
+         * - the pair $app/$key cannot exist in both array,
453
+         * - we should still return an existing non-lazy value even if current method
454
+         *   is called with $lazy is true
455
+         *
456
+         * This way, lazyCache will be empty until the load for lazy config value is requested.
457
+         */
458
+        if (isset($this->lazyCache[$app][$key])) {
459
+            $value = $this->lazyCache[$app][$key];
460
+        } elseif (isset($this->fastCache[$app][$key])) {
461
+            $value = $this->fastCache[$app][$key];
462
+        } else {
463
+            return $default;
464
+        }
465
+
466
+        $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $knownType);
467
+        if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
468
+            // Only decrypt values that are stored encrypted
469
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
470
+        }
471
+
472
+        return $value;
473
+    }
474
+
475
+    /**
476
+     * @inheritDoc
477
+     *
478
+     * @param string $app id of the app
479
+     * @param string $key config key
480
+     *
481
+     * @return int type of the value
482
+     * @throws AppConfigUnknownKeyException if config key is not known
483
+     * @since 29.0.0
484
+     * @see VALUE_STRING
485
+     * @see VALUE_INT
486
+     * @see VALUE_FLOAT
487
+     * @see VALUE_BOOL
488
+     * @see VALUE_ARRAY
489
+     */
490
+    public function getValueType(string $app, string $key, ?bool $lazy = null): int {
491
+        $type = self::VALUE_MIXED;
492
+        $ignorable = $lazy ?? false;
493
+        $this->matchAndApplyLexiconDefinition($app, $key, $ignorable, $type);
494
+        if ($type !== self::VALUE_MIXED) {
495
+            // a modified $type means config key is set in Lexicon
496
+            return $type;
497
+        }
498
+
499
+        $this->assertParams($app, $key);
500
+        $this->loadConfig($app, $lazy);
501
+
502
+        if (!isset($this->valueTypes[$app][$key])) {
503
+            throw new AppConfigUnknownKeyException('unknown config key');
504
+        }
505
+
506
+        $type = $this->valueTypes[$app][$key];
507
+        $type &= ~self::VALUE_SENSITIVE;
508
+        return $type;
509
+    }
510
+
511
+
512
+    /**
513
+     * Store a config key and its value in database as VALUE_MIXED
514
+     *
515
+     * **WARNING:** Method is internal and **MUST** not be used as it is best to set a real value type
516
+     *
517
+     * @param string $app id of the app
518
+     * @param string $key config key
519
+     * @param string $value config value
520
+     * @param bool $lazy set config as lazy loaded
521
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
522
+     *
523
+     * @return bool TRUE if value was different, therefor updated in database
524
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED
525
+     * @internal
526
+     * @since 29.0.0
527
+     * @see IAppConfig for explanation about lazy loading
528
+     * @see setValueString()
529
+     * @see setValueInt()
530
+     * @see setValueFloat()
531
+     * @see setValueBool()
532
+     * @see setValueArray()
533
+     */
534
+    public function setValueMixed(
535
+        string $app,
536
+        string $key,
537
+        string $value,
538
+        bool $lazy = false,
539
+        bool $sensitive = false,
540
+    ): bool {
541
+        return $this->setTypedValue(
542
+            $app,
543
+            $key,
544
+            $value,
545
+            $lazy,
546
+            self::VALUE_MIXED | ($sensitive ? self::VALUE_SENSITIVE : 0)
547
+        );
548
+    }
549
+
550
+
551
+    /**
552
+     * @inheritDoc
553
+     *
554
+     * @param string $app id of the app
555
+     * @param string $key config key
556
+     * @param string $value config value
557
+     * @param bool $lazy set config as lazy loaded
558
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
559
+     *
560
+     * @return bool TRUE if value was different, therefor updated in database
561
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
562
+     * @since 29.0.0
563
+     * @see IAppConfig for explanation about lazy loading
564
+     */
565
+    public function setValueString(
566
+        string $app,
567
+        string $key,
568
+        string $value,
569
+        bool $lazy = false,
570
+        bool $sensitive = false,
571
+    ): bool {
572
+        return $this->setTypedValue(
573
+            $app,
574
+            $key,
575
+            $value,
576
+            $lazy,
577
+            self::VALUE_STRING | ($sensitive ? self::VALUE_SENSITIVE : 0)
578
+        );
579
+    }
580
+
581
+    /**
582
+     * @inheritDoc
583
+     *
584
+     * @param string $app id of the app
585
+     * @param string $key config key
586
+     * @param int $value config value
587
+     * @param bool $lazy set config as lazy loaded
588
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
589
+     *
590
+     * @return bool TRUE if value was different, therefor updated in database
591
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
592
+     * @since 29.0.0
593
+     * @see IAppConfig for explanation about lazy loading
594
+     */
595
+    public function setValueInt(
596
+        string $app,
597
+        string $key,
598
+        int $value,
599
+        bool $lazy = false,
600
+        bool $sensitive = false,
601
+    ): bool {
602
+        if ($value > 2000000000) {
603
+            $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.');
604
+        }
605
+
606
+        return $this->setTypedValue(
607
+            $app,
608
+            $key,
609
+            (string)$value,
610
+            $lazy,
611
+            self::VALUE_INT | ($sensitive ? self::VALUE_SENSITIVE : 0)
612
+        );
613
+    }
614
+
615
+    /**
616
+     * @inheritDoc
617
+     *
618
+     * @param string $app id of the app
619
+     * @param string $key config key
620
+     * @param float $value config value
621
+     * @param bool $lazy set config as lazy loaded
622
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
623
+     *
624
+     * @return bool TRUE if value was different, therefor updated in database
625
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
626
+     * @since 29.0.0
627
+     * @see IAppConfig for explanation about lazy loading
628
+     */
629
+    public function setValueFloat(
630
+        string $app,
631
+        string $key,
632
+        float $value,
633
+        bool $lazy = false,
634
+        bool $sensitive = false,
635
+    ): bool {
636
+        return $this->setTypedValue(
637
+            $app,
638
+            $key,
639
+            (string)$value,
640
+            $lazy,
641
+            self::VALUE_FLOAT | ($sensitive ? self::VALUE_SENSITIVE : 0)
642
+        );
643
+    }
644
+
645
+    /**
646
+     * @inheritDoc
647
+     *
648
+     * @param string $app id of the app
649
+     * @param string $key config key
650
+     * @param bool $value config value
651
+     * @param bool $lazy set config as lazy loaded
652
+     *
653
+     * @return bool TRUE if value was different, therefor updated in database
654
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
655
+     * @since 29.0.0
656
+     * @see IAppConfig for explanation about lazy loading
657
+     */
658
+    public function setValueBool(
659
+        string $app,
660
+        string $key,
661
+        bool $value,
662
+        bool $lazy = false,
663
+    ): bool {
664
+        return $this->setTypedValue(
665
+            $app,
666
+            $key,
667
+            ($value) ? '1' : '0',
668
+            $lazy,
669
+            self::VALUE_BOOL
670
+        );
671
+    }
672
+
673
+    /**
674
+     * @inheritDoc
675
+     *
676
+     * @param string $app id of the app
677
+     * @param string $key config key
678
+     * @param array $value config value
679
+     * @param bool $lazy set config as lazy loaded
680
+     * @param bool $sensitive if TRUE value will be hidden when listing config values.
681
+     *
682
+     * @return bool TRUE if value was different, therefor updated in database
683
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
684
+     * @throws JsonException
685
+     * @since 29.0.0
686
+     * @see IAppConfig for explanation about lazy loading
687
+     */
688
+    public function setValueArray(
689
+        string $app,
690
+        string $key,
691
+        array $value,
692
+        bool $lazy = false,
693
+        bool $sensitive = false,
694
+    ): bool {
695
+        try {
696
+            return $this->setTypedValue(
697
+                $app,
698
+                $key,
699
+                json_encode($value, JSON_THROW_ON_ERROR),
700
+                $lazy,
701
+                self::VALUE_ARRAY | ($sensitive ? self::VALUE_SENSITIVE : 0)
702
+            );
703
+        } catch (JsonException $e) {
704
+            $this->logger->warning('could not setValueArray', ['app' => $app, 'key' => $key, 'exception' => $e]);
705
+            throw $e;
706
+        }
707
+    }
708
+
709
+    /**
710
+     * Store a config key and its value in database
711
+     *
712
+     * If config key is already known with the exact same config value and same sensitive/lazy status, the
713
+     * database is not updated. If config value was previously stored as sensitive, status will not be
714
+     * altered.
715
+     *
716
+     * @param string $app id of the app
717
+     * @param string $key config key
718
+     * @param string $value config value
719
+     * @param bool $lazy config set as lazy loaded
720
+     * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
721
+     *
722
+     * @return bool TRUE if value was updated in database
723
+     * @throws AppConfigTypeConflictException if type from database is not VALUE_MIXED and different from the requested one
724
+     * @see IAppConfig for explanation about lazy loading
725
+     */
726
+    private function setTypedValue(
727
+        string $app,
728
+        string $key,
729
+        string $value,
730
+        bool $lazy,
731
+        int $type,
732
+    ): bool {
733
+        $this->assertParams($app, $key);
734
+        if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
735
+            return false; // returns false as database is not updated
736
+        }
737
+        $this->loadConfig(null, $lazy);
738
+
739
+        $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
740
+        $inserted = $refreshCache = false;
741
+
742
+        $origValue = $value;
743
+        if ($sensitive || ($this->hasKey($app, $key, $lazy) && $this->isSensitive($app, $key, $lazy))) {
744
+            $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
745
+        }
746
+
747
+        if ($this->hasKey($app, $key, $lazy)) {
748
+            /**
749
+             * no update if key is already known with set lazy status and value is
750
+             * not different, unless sensitivity is switched from false to true.
751
+             */
752
+            if ($origValue === $this->getTypedValue($app, $key, $value, $lazy, $type)
753
+                && (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
754
+                return false;
755
+            }
756
+        } else {
757
+            /**
758
+             * if key is not known yet, we try to insert.
759
+             * It might fail if the key exists with a different lazy flag.
760
+             */
761
+            try {
762
+                $insert = $this->connection->getQueryBuilder();
763
+                $insert->insert('appconfig')
764
+                    ->setValue('appid', $insert->createNamedParameter($app))
765
+                    ->setValue('lazy', $insert->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
766
+                    ->setValue('type', $insert->createNamedParameter($type, IQueryBuilder::PARAM_INT))
767
+                    ->setValue('configkey', $insert->createNamedParameter($key))
768
+                    ->setValue('configvalue', $insert->createNamedParameter($value));
769
+                $insert->executeStatement();
770
+                $inserted = true;
771
+            } catch (DBException $e) {
772
+                if ($e->getReason() !== DBException::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
773
+                    throw $e; // TODO: throw exception or just log and returns false !?
774
+                }
775
+            }
776
+        }
777
+
778
+        /**
779
+         * We cannot insert a new row, meaning we need to update an already existing one
780
+         */
781
+        if (!$inserted) {
782
+            $currType = $this->valueTypes[$app][$key] ?? 0;
783
+            if ($currType === 0) { // this might happen when switching lazy loading status
784
+                $this->loadConfigAll();
785
+                $currType = $this->valueTypes[$app][$key] ?? 0;
786
+            }
787
+
788
+            /**
789
+             * This should only happen during the upgrade process from 28 to 29.
790
+             * We only log a warning and set it to VALUE_MIXED.
791
+             */
792
+            if ($currType === 0) {
793
+                $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]);
794
+                $currType = self::VALUE_MIXED;
795
+            }
796
+
797
+            /**
798
+             * we only accept a different type from the one stored in database
799
+             * if the one stored in database is not-defined (VALUE_MIXED)
800
+             */
801
+            if (!$this->isTyped(self::VALUE_MIXED, $currType) &&
802
+                ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
803
+                try {
804
+                    $currType = $this->convertTypeToString($currType);
805
+                    $type = $this->convertTypeToString($type);
806
+                } catch (AppConfigIncorrectTypeException) {
807
+                    // can be ignored, this was just needed for a better exception message.
808
+                }
809
+                throw new AppConfigTypeConflictException('conflict between new type (' . $type . ') and old type (' . $currType . ')');
810
+            }
811
+
812
+            // we fix $type if the stored value, or the new value as it might be changed, is set as sensitive
813
+            if ($sensitive || $this->isTyped(self::VALUE_SENSITIVE, $currType)) {
814
+                $type |= self::VALUE_SENSITIVE;
815
+            }
816
+
817
+            if ($lazy !== $this->isLazy($app, $key)) {
818
+                $refreshCache = true;
819
+            }
820
+
821
+            $update = $this->connection->getQueryBuilder();
822
+            $update->update('appconfig')
823
+                ->set('configvalue', $update->createNamedParameter($value))
824
+                ->set('lazy', $update->createNamedParameter(($lazy) ? 1 : 0, IQueryBuilder::PARAM_INT))
825
+                ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
826
+                ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
827
+                ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
828
+
829
+            $update->executeStatement();
830
+        }
831
+
832
+        if ($refreshCache) {
833
+            $this->clearCache();
834
+            return true;
835
+        }
836
+
837
+        // update local cache
838
+        if ($lazy) {
839
+            $this->lazyCache[$app][$key] = $value;
840
+        } else {
841
+            $this->fastCache[$app][$key] = $value;
842
+        }
843
+        $this->valueTypes[$app][$key] = $type;
844
+
845
+        return true;
846
+    }
847
+
848
+    /**
849
+     * Change the type of config value.
850
+     *
851
+     * **WARNING:** Method is internal and **MUST** not be used as it may break things.
852
+     *
853
+     * @param string $app id of the app
854
+     * @param string $key config key
855
+     * @param int $type value type {@see VALUE_STRING} {@see VALUE_INT} {@see VALUE_FLOAT} {@see VALUE_BOOL} {@see VALUE_ARRAY}
856
+     *
857
+     * @return bool TRUE if database update were necessary
858
+     * @throws AppConfigUnknownKeyException if $key is now known in database
859
+     * @throws AppConfigIncorrectTypeException if $type is not valid
860
+     * @internal
861
+     * @since 29.0.0
862
+     */
863
+    public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
864
+        $this->assertParams($app, $key);
865
+        $this->loadConfigAll();
866
+        $lazy = $this->isLazy($app, $key);
867
+
868
+        // type can only be one type
869
+        if (!in_array($type, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
870
+            throw new AppConfigIncorrectTypeException('Unknown value type');
871
+        }
872
+
873
+        $currType = $this->valueTypes[$app][$key];
874
+        if (($type | self::VALUE_SENSITIVE) === ($currType | self::VALUE_SENSITIVE)) {
875
+            return false;
876
+        }
877
+
878
+        // we complete with sensitive flag if the stored value is set as sensitive
879
+        if ($this->isTyped(self::VALUE_SENSITIVE, $currType)) {
880
+            $type = $type | self::VALUE_SENSITIVE;
881
+        }
882
+
883
+        $update = $this->connection->getQueryBuilder();
884
+        $update->update('appconfig')
885
+            ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
886
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
887
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
888
+        $update->executeStatement();
889
+        $this->valueTypes[$app][$key] = $type;
890
+
891
+        return true;
892
+    }
893
+
894
+
895
+    /**
896
+     * @inheritDoc
897
+     *
898
+     * @param string $app id of the app
899
+     * @param string $key config key
900
+     * @param bool $sensitive TRUE to set as sensitive, FALSE to unset
901
+     *
902
+     * @return bool TRUE if entry was found in database and an update was necessary
903
+     * @since 29.0.0
904
+     */
905
+    public function updateSensitive(string $app, string $key, bool $sensitive): bool {
906
+        $this->assertParams($app, $key);
907
+        $this->loadConfigAll();
908
+
909
+        try {
910
+            if ($sensitive === $this->isSensitive($app, $key, null)) {
911
+                return false;
912
+            }
913
+        } catch (AppConfigUnknownKeyException $e) {
914
+            return false;
915
+        }
916
+
917
+        $lazy = $this->isLazy($app, $key);
918
+        if ($lazy) {
919
+            $cache = $this->lazyCache;
920
+        } else {
921
+            $cache = $this->fastCache;
922
+        }
923
+
924
+        if (!isset($cache[$app][$key])) {
925
+            throw new AppConfigUnknownKeyException('unknown config key');
926
+        }
927
+
928
+        /**
929
+         * type returned by getValueType() is already cleaned from sensitive flag
930
+         * we just need to update it based on $sensitive and store it in database
931
+         */
932
+        $type = $this->getValueType($app, $key);
933
+        $value = $cache[$app][$key];
934
+        if ($sensitive) {
935
+            $type |= self::VALUE_SENSITIVE;
936
+            $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value);
937
+        } else {
938
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
939
+        }
940
+
941
+        $update = $this->connection->getQueryBuilder();
942
+        $update->update('appconfig')
943
+            ->set('type', $update->createNamedParameter($type, IQueryBuilder::PARAM_INT))
944
+            ->set('configvalue', $update->createNamedParameter($value))
945
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
946
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
947
+        $update->executeStatement();
948
+
949
+        $this->valueTypes[$app][$key] = $type;
950
+
951
+        return true;
952
+    }
953
+
954
+    /**
955
+     * @inheritDoc
956
+     *
957
+     * @param string $app id of the app
958
+     * @param string $key config key
959
+     * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset
960
+     *
961
+     * @return bool TRUE if entry was found in database and an update was necessary
962
+     * @since 29.0.0
963
+     */
964
+    public function updateLazy(string $app, string $key, bool $lazy): bool {
965
+        $this->assertParams($app, $key);
966
+        $this->loadConfigAll();
967
+
968
+        try {
969
+            if ($lazy === $this->isLazy($app, $key)) {
970
+                return false;
971
+            }
972
+        } catch (AppConfigUnknownKeyException $e) {
973
+            return false;
974
+        }
975
+
976
+        $update = $this->connection->getQueryBuilder();
977
+        $update->update('appconfig')
978
+            ->set('lazy', $update->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT))
979
+            ->where($update->expr()->eq('appid', $update->createNamedParameter($app)))
980
+            ->andWhere($update->expr()->eq('configkey', $update->createNamedParameter($key)));
981
+        $update->executeStatement();
982
+
983
+        // At this point, it is a lot safer to clean cache
984
+        $this->clearCache();
985
+
986
+        return true;
987
+    }
988
+
989
+    /**
990
+     * @inheritDoc
991
+     *
992
+     * @param string $app id of the app
993
+     * @param string $key config key
994
+     *
995
+     * @return array
996
+     * @throws AppConfigUnknownKeyException if config key is not known in database
997
+     * @since 29.0.0
998
+     */
999
+    public function getDetails(string $app, string $key): array {
1000
+        $this->assertParams($app, $key);
1001
+        $this->loadConfigAll();
1002
+        $lazy = $this->isLazy($app, $key);
1003
+
1004
+        if ($lazy) {
1005
+            $cache = $this->lazyCache;
1006
+        } else {
1007
+            $cache = $this->fastCache;
1008
+        }
1009
+
1010
+        $type = $this->getValueType($app, $key);
1011
+        try {
1012
+            $typeString = $this->convertTypeToString($type);
1013
+        } catch (AppConfigIncorrectTypeException $e) {
1014
+            $this->logger->warning('type stored in database is not correct', ['exception' => $e, 'type' => $type]);
1015
+            $typeString = (string)$type;
1016
+        }
1017
+
1018
+        if (!isset($cache[$app][$key])) {
1019
+            throw new AppConfigUnknownKeyException('unknown config key');
1020
+        }
1021
+
1022
+        $value = $cache[$app][$key];
1023
+        $sensitive = $this->isSensitive($app, $key, null);
1024
+        if ($sensitive && str_starts_with($value, self::ENCRYPTION_PREFIX)) {
1025
+            $value = $this->crypto->decrypt(substr($value, self::ENCRYPTION_PREFIX_LENGTH));
1026
+        }
1027
+
1028
+        return [
1029
+            'app' => $app,
1030
+            'key' => $key,
1031
+            'value' => $value,
1032
+            'type' => $type,
1033
+            'lazy' => $lazy,
1034
+            'typeString' => $typeString,
1035
+            'sensitive' => $sensitive
1036
+        ];
1037
+    }
1038
+
1039
+    /**
1040
+     * @param string $type
1041
+     *
1042
+     * @return int
1043
+     * @throws AppConfigIncorrectTypeException
1044
+     * @since 29.0.0
1045
+     */
1046
+    public function convertTypeToInt(string $type): int {
1047
+        return match (strtolower($type)) {
1048
+            'mixed' => IAppConfig::VALUE_MIXED,
1049
+            'string' => IAppConfig::VALUE_STRING,
1050
+            'integer' => IAppConfig::VALUE_INT,
1051
+            'float' => IAppConfig::VALUE_FLOAT,
1052
+            'boolean' => IAppConfig::VALUE_BOOL,
1053
+            'array' => IAppConfig::VALUE_ARRAY,
1054
+            default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
1055
+        };
1056
+    }
1057
+
1058
+    /**
1059
+     * @param int $type
1060
+     *
1061
+     * @return string
1062
+     * @throws AppConfigIncorrectTypeException
1063
+     * @since 29.0.0
1064
+     */
1065
+    public function convertTypeToString(int $type): string {
1066
+        $type &= ~self::VALUE_SENSITIVE;
1067
+
1068
+        return match ($type) {
1069
+            IAppConfig::VALUE_MIXED => 'mixed',
1070
+            IAppConfig::VALUE_STRING => 'string',
1071
+            IAppConfig::VALUE_INT => 'integer',
1072
+            IAppConfig::VALUE_FLOAT => 'float',
1073
+            IAppConfig::VALUE_BOOL => 'boolean',
1074
+            IAppConfig::VALUE_ARRAY => 'array',
1075
+            default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
1076
+        };
1077
+    }
1078
+
1079
+    /**
1080
+     * @inheritDoc
1081
+     *
1082
+     * @param string $app id of the app
1083
+     * @param string $key config key
1084
+     *
1085
+     * @since 29.0.0
1086
+     */
1087
+    public function deleteKey(string $app, string $key): void {
1088
+        $this->assertParams($app, $key);
1089
+        $qb = $this->connection->getQueryBuilder();
1090
+        $qb->delete('appconfig')
1091
+            ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)))
1092
+            ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key)));
1093
+        $qb->executeStatement();
1094
+
1095
+        unset($this->lazyCache[$app][$key]);
1096
+        unset($this->fastCache[$app][$key]);
1097
+        unset($this->valueTypes[$app][$key]);
1098
+    }
1099
+
1100
+    /**
1101
+     * @inheritDoc
1102
+     *
1103
+     * @param string $app id of the app
1104
+     *
1105
+     * @since 29.0.0
1106
+     */
1107
+    public function deleteApp(string $app): void {
1108
+        $this->assertParams($app);
1109
+        $qb = $this->connection->getQueryBuilder();
1110
+        $qb->delete('appconfig')
1111
+            ->where($qb->expr()->eq('appid', $qb->createNamedParameter($app)));
1112
+        $qb->executeStatement();
1113
+
1114
+        $this->clearCache();
1115
+    }
1116
+
1117
+    /**
1118
+     * @inheritDoc
1119
+     *
1120
+     * @param bool $reload set to TRUE to refill cache instantly after clearing it
1121
+     *
1122
+     * @since 29.0.0
1123
+     */
1124
+    public function clearCache(bool $reload = false): void {
1125
+        $this->lazyLoaded = $this->fastLoaded = false;
1126
+        $this->lazyCache = $this->fastCache = $this->valueTypes = [];
1127
+
1128
+        if (!$reload) {
1129
+            return;
1130
+        }
1131
+
1132
+        $this->loadConfigAll();
1133
+    }
1134
+
1135
+
1136
+    /**
1137
+     * For debug purpose.
1138
+     * Returns the cached data.
1139
+     *
1140
+     * @return array
1141
+     * @since 29.0.0
1142
+     * @internal
1143
+     */
1144
+    public function statusCache(): array {
1145
+        return [
1146
+            'fastLoaded' => $this->fastLoaded,
1147
+            'fastCache' => $this->fastCache,
1148
+            'lazyLoaded' => $this->lazyLoaded,
1149
+            'lazyCache' => $this->lazyCache,
1150
+        ];
1151
+    }
1152
+
1153
+    /**
1154
+     * @param int $needle bitflag to search
1155
+     * @param int $type known value
1156
+     *
1157
+     * @return bool TRUE if bitflag $needle is set in $type
1158
+     */
1159
+    private function isTyped(int $needle, int $type): bool {
1160
+        return (($needle & $type) !== 0);
1161
+    }
1162
+
1163
+    /**
1164
+     * Confirm the string set for app and key fit the database description
1165
+     *
1166
+     * @param string $app assert $app fit in database
1167
+     * @param string $configKey assert config key fit in database
1168
+     * @param bool $allowEmptyApp $app can be empty string
1169
+     * @param int $valueType assert value type is only one type
1170
+     *
1171
+     * @throws InvalidArgumentException
1172
+     */
1173
+    private function assertParams(string $app = '', string $configKey = '', bool $allowEmptyApp = false, int $valueType = -1): void {
1174
+        if (!$allowEmptyApp && $app === '') {
1175
+            throw new InvalidArgumentException('app cannot be an empty string');
1176
+        }
1177
+        if (strlen($app) > self::APP_MAX_LENGTH) {
1178
+            throw new InvalidArgumentException(
1179
+                'Value (' . $app . ') for app is too long (' . self::APP_MAX_LENGTH . ')'
1180
+            );
1181
+        }
1182
+        if (strlen($configKey) > self::KEY_MAX_LENGTH) {
1183
+            throw new InvalidArgumentException('Value (' . $configKey . ') for key is too long (' . self::KEY_MAX_LENGTH . ')');
1184
+        }
1185
+        if ($valueType > -1) {
1186
+            $valueType &= ~self::VALUE_SENSITIVE;
1187
+            if (!in_array($valueType, [self::VALUE_MIXED, self::VALUE_STRING, self::VALUE_INT, self::VALUE_FLOAT, self::VALUE_BOOL, self::VALUE_ARRAY])) {
1188
+                throw new InvalidArgumentException('Unknown value type');
1189
+            }
1190
+        }
1191
+    }
1192
+
1193
+    private function loadConfigAll(?string $app = null): void {
1194
+        $this->loadConfig($app, null);
1195
+    }
1196
+
1197
+    /**
1198
+     * Load normal config or config set as lazy loaded
1199
+     *
1200
+     * @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
1201
+     */
1202
+    private function loadConfig(?string $app = null, ?bool $lazy = false): void {
1203
+        if ($this->isLoaded($lazy)) {
1204
+            return;
1205
+        }
1206
+
1207
+        // if lazy is null or true, we debug log
1208
+        if (($lazy ?? true) !== false && $app !== null) {
1209
+            $exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "' . $app . '"');
1210
+            $this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
1211
+        }
1212
+
1213
+        $qb = $this->connection->getQueryBuilder();
1214
+        $qb->from('appconfig');
1215
+
1216
+        // we only need value from lazy when loadConfig does not specify it
1217
+        $qb->select('appid', 'configkey', 'configvalue', 'type');
1218
+
1219
+        if ($lazy !== null) {
1220
+            $qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
1221
+        } else {
1222
+            $qb->addSelect('lazy');
1223
+        }
1224
+
1225
+        $result = $qb->executeQuery();
1226
+        $rows = $result->fetchAll();
1227
+        foreach ($rows as $row) {
1228
+            // most of the time, 'lazy' is not in the select because its value is already known
1229
+            if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
1230
+                $this->lazyCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1231
+            } else {
1232
+                $this->fastCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
1233
+            }
1234
+            $this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
1235
+        }
1236
+        $result->closeCursor();
1237
+        $this->setAsLoaded($lazy);
1238
+    }
1239
+
1240
+    /**
1241
+     * if $lazy is:
1242
+     *  - false: will returns true if fast config is loaded
1243
+     *  - true : will returns true if lazy config is loaded
1244
+     *  - null : will returns true if both config are loaded
1245
+     *
1246
+     * @param bool $lazy
1247
+     *
1248
+     * @return bool
1249
+     */
1250
+    private function isLoaded(?bool $lazy): bool {
1251
+        if ($lazy === null) {
1252
+            return $this->lazyLoaded && $this->fastLoaded;
1253
+        }
1254
+
1255
+        return $lazy ? $this->lazyLoaded : $this->fastLoaded;
1256
+    }
1257
+
1258
+    /**
1259
+     * if $lazy is:
1260
+     * - false: set fast config as loaded
1261
+     * - true : set lazy config as loaded
1262
+     * - null : set both config as loaded
1263
+     *
1264
+     * @param bool $lazy
1265
+     */
1266
+    private function setAsLoaded(?bool $lazy): void {
1267
+        if ($lazy === null) {
1268
+            $this->fastLoaded = true;
1269
+            $this->lazyLoaded = true;
1270
+
1271
+            return;
1272
+        }
1273
+
1274
+        if ($lazy) {
1275
+            $this->lazyLoaded = true;
1276
+        } else {
1277
+            $this->fastLoaded = true;
1278
+        }
1279
+    }
1280
+
1281
+    /**
1282
+     * Gets the config value
1283
+     *
1284
+     * @param string $app app
1285
+     * @param string $key key
1286
+     * @param string $default = null, default value if the key does not exist
1287
+     *
1288
+     * @return string the value or $default
1289
+     * @deprecated 29.0.0 use getValue*()
1290
+     *
1291
+     * This function gets a value from the appconfig table. If the key does
1292
+     * not exist the default value will be returned
1293
+     */
1294
+    public function getValue($app, $key, $default = null) {
1295
+        $this->loadConfig($app);
1296
+
1297
+        return $this->fastCache[$app][$key] ?? $default;
1298
+    }
1299
+
1300
+    /**
1301
+     * Sets a value. If the key did not exist before it will be created.
1302
+     *
1303
+     * @param string $app app
1304
+     * @param string $key key
1305
+     * @param string|float|int $value value
1306
+     *
1307
+     * @return bool True if the value was inserted or updated, false if the value was the same
1308
+     * @throws AppConfigTypeConflictException
1309
+     * @throws AppConfigUnknownKeyException
1310
+     * @deprecated 29.0.0
1311
+     */
1312
+    public function setValue($app, $key, $value) {
1313
+        /**
1314
+         * TODO: would it be overkill, or decently improve performance, to catch
1315
+         * call to this method with $key='enabled' and 'hide' config value related
1316
+         * to $app when the app is disabled (by modifying entry in database: lazy=lazy+2)
1317
+         * or enabled (lazy=lazy-2)
1318
+         *
1319
+         * this solution would remove the loading of config values from disabled app
1320
+         * unless calling the method {@see loadConfigAll()}
1321
+         */
1322
+        return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
1323
+    }
1324
+
1325
+
1326
+    /**
1327
+     * get multiple values, either the app or key can be used as wildcard by setting it to false
1328
+     *
1329
+     * @param string|false $app
1330
+     * @param string|false $key
1331
+     *
1332
+     * @return array|false
1333
+     * @deprecated 29.0.0 use {@see getAllValues()}
1334
+     */
1335
+    public function getValues($app, $key) {
1336
+        if (($app !== false) === ($key !== false)) {
1337
+            return false;
1338
+        }
1339
+
1340
+        $key = ($key === false) ? '' : $key;
1341
+        if (!$app) {
1342
+            return $this->searchValues($key, false, self::VALUE_MIXED);
1343
+        } else {
1344
+            return $this->getAllValues($app, $key);
1345
+        }
1346
+    }
1347
+
1348
+    /**
1349
+     * get all values of the app or and filters out sensitive data
1350
+     *
1351
+     * @param string $app
1352
+     *
1353
+     * @return array
1354
+     * @deprecated 29.0.0 use {@see getAllValues()}
1355
+     */
1356
+    public function getFilteredValues($app) {
1357
+        return $this->getAllValues($app, filtered: true);
1358
+    }
1359
+
1360
+
1361
+    /**
1362
+     * **Warning:** avoid default NULL value for $lazy as this will
1363
+     * load all lazy values from the database
1364
+     *
1365
+     * @param string $app
1366
+     * @param array<string, string> $values ['key' => 'value']
1367
+     * @param bool|null $lazy
1368
+     *
1369
+     * @return array<string, string|int|float|bool|array>
1370
+     */
1371
+    private function formatAppValues(string $app, array $values, ?bool $lazy = null): array {
1372
+        foreach ($values as $key => $value) {
1373
+            try {
1374
+                $type = $this->getValueType($app, $key, $lazy);
1375
+            } catch (AppConfigUnknownKeyException $e) {
1376
+                continue;
1377
+            }
1378
+
1379
+            $values[$key] = $this->convertTypedValue($value, $type);
1380
+        }
1381
+
1382
+        return $values;
1383
+    }
1384
+
1385
+    /**
1386
+     * convert string value to the expected type
1387
+     *
1388
+     * @param string $value
1389
+     * @param int $type
1390
+     *
1391
+     * @return string|int|float|bool|array
1392
+     */
1393
+    private function convertTypedValue(string $value, int $type): string|int|float|bool|array {
1394
+        switch ($type) {
1395
+            case self::VALUE_INT:
1396
+                return (int)$value;
1397
+            case self::VALUE_FLOAT:
1398
+                return (float)$value;
1399
+            case self::VALUE_BOOL:
1400
+                return in_array(strtolower($value), ['1', 'true', 'yes', 'on']);
1401
+            case self::VALUE_ARRAY:
1402
+                try {
1403
+                    return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
1404
+                } catch (JsonException $e) {
1405
+                    // ignoreable
1406
+                }
1407
+                break;
1408
+        }
1409
+        return $value;
1410
+    }
1411
+
1412
+    /**
1413
+     * @param string $app
1414
+     *
1415
+     * @return string[]
1416
+     * @deprecated 29.0.0 data sensitivity should be set when calling setValue*()
1417
+     */
1418
+    private function getSensitiveKeys(string $app): array {
1419
+        $sensitiveValues = [
1420
+            'circles' => [
1421
+                '/^key_pairs$/',
1422
+                '/^local_gskey$/',
1423
+            ],
1424
+            'call_summary_bot' => [
1425
+                '/^secret_(.*)$/',
1426
+            ],
1427
+            'external' => [
1428
+                '/^sites$/',
1429
+                '/^jwt_token_privkey_(.*)$/',
1430
+            ],
1431
+            'globalsiteselector' => [
1432
+                '/^gss\.jwt\.key$/',
1433
+            ],
1434
+            'gpgmailer' => [
1435
+                '/^GpgServerKey$/',
1436
+            ],
1437
+            'integration_discourse' => [
1438
+                '/^private_key$/',
1439
+                '/^public_key$/',
1440
+            ],
1441
+            'integration_dropbox' => [
1442
+                '/^client_id$/',
1443
+                '/^client_secret$/',
1444
+            ],
1445
+            'integration_github' => [
1446
+                '/^client_id$/',
1447
+                '/^client_secret$/',
1448
+            ],
1449
+            'integration_gitlab' => [
1450
+                '/^client_id$/',
1451
+                '/^client_secret$/',
1452
+                '/^oauth_instance_url$/',
1453
+            ],
1454
+            'integration_google' => [
1455
+                '/^client_id$/',
1456
+                '/^client_secret$/',
1457
+            ],
1458
+            'integration_jira' => [
1459
+                '/^client_id$/',
1460
+                '/^client_secret$/',
1461
+                '/^forced_instance_url$/',
1462
+            ],
1463
+            'integration_onedrive' => [
1464
+                '/^client_id$/',
1465
+                '/^client_secret$/',
1466
+            ],
1467
+            'integration_openproject' => [
1468
+                '/^client_id$/',
1469
+                '/^client_secret$/',
1470
+                '/^oauth_instance_url$/',
1471
+            ],
1472
+            'integration_reddit' => [
1473
+                '/^client_id$/',
1474
+                '/^client_secret$/',
1475
+            ],
1476
+            'integration_suitecrm' => [
1477
+                '/^client_id$/',
1478
+                '/^client_secret$/',
1479
+                '/^oauth_instance_url$/',
1480
+            ],
1481
+            'integration_twitter' => [
1482
+                '/^consumer_key$/',
1483
+                '/^consumer_secret$/',
1484
+                '/^followed_user$/',
1485
+            ],
1486
+            'integration_zammad' => [
1487
+                '/^client_id$/',
1488
+                '/^client_secret$/',
1489
+                '/^oauth_instance_url$/',
1490
+            ],
1491
+            'maps' => [
1492
+                '/^mapboxAPIKEY$/',
1493
+            ],
1494
+            'notify_push' => [
1495
+                '/^cookie$/',
1496
+            ],
1497
+            'onlyoffice' => [
1498
+                '/^jwt_secret$/',
1499
+            ],
1500
+            'passwords' => [
1501
+                '/^SSEv1ServerKey$/',
1502
+            ],
1503
+            'serverinfo' => [
1504
+                '/^token$/',
1505
+            ],
1506
+            'spreed' => [
1507
+                '/^bridge_bot_password$/',
1508
+                '/^hosted-signaling-server-(.*)$/',
1509
+                '/^recording_servers$/',
1510
+                '/^signaling_servers$/',
1511
+                '/^signaling_ticket_secret$/',
1512
+                '/^signaling_token_privkey_(.*)$/',
1513
+                '/^signaling_token_pubkey_(.*)$/',
1514
+                '/^sip_bridge_dialin_info$/',
1515
+                '/^sip_bridge_shared_secret$/',
1516
+                '/^stun_servers$/',
1517
+                '/^turn_servers$/',
1518
+                '/^turn_server_secret$/',
1519
+            ],
1520
+            'support' => [
1521
+                '/^last_response$/',
1522
+                '/^potential_subscription_key$/',
1523
+                '/^subscription_key$/',
1524
+            ],
1525
+            'theming' => [
1526
+                '/^imprintUrl$/',
1527
+                '/^privacyUrl$/',
1528
+                '/^slogan$/',
1529
+                '/^url$/',
1530
+            ],
1531
+            'twofactor_gateway' => [
1532
+                '/^.*token$/',
1533
+            ],
1534
+            'user_ldap' => [
1535
+                '/^(s..)?ldap_agent_password$/',
1536
+            ],
1537
+            'user_saml' => [
1538
+                '/^idp-x509cert$/',
1539
+            ],
1540
+            'whiteboard' => [
1541
+                '/^jwt_secret_key$/',
1542
+            ],
1543
+        ];
1544
+
1545
+        return $sensitiveValues[$app] ?? [];
1546
+    }
1547
+
1548
+    /**
1549
+     * Clear all the cached app config values
1550
+     * New cache will be generated next time a config value is retrieved
1551
+     *
1552
+     * @deprecated 29.0.0 use {@see clearCache()}
1553
+     */
1554
+    public function clearCachedConfig(): void {
1555
+        $this->clearCache();
1556
+    }
1557
+
1558
+    /**
1559
+     * match and apply current use of config values with defined lexicon
1560
+     *
1561
+     * @throws AppConfigUnknownKeyException
1562
+     * @throws AppConfigTypeConflictException
1563
+     * @return bool TRUE if everything is fine compared to lexicon or lexicon does not exist
1564
+     */
1565
+    private function matchAndApplyLexiconDefinition(
1566
+        string $app,
1567
+        string $key,
1568
+        bool &$lazy,
1569
+        int &$type,
1570
+        string &$default = '',
1571
+    ): bool {
1572
+        if (in_array($key,
1573
+            [
1574
+                'enabled',
1575
+                'installed_version',
1576
+                'types',
1577
+            ])) {
1578
+            return true; // we don't break stuff for this list of config keys.
1579
+        }
1580
+        $configDetails = $this->getConfigDetailsFromLexicon($app);
1581
+        if (!array_key_exists($key, $configDetails['entries'])) {
1582
+            return $this->applyLexiconStrictness(
1583
+                $configDetails['strictness'],
1584
+                'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon'
1585
+            );
1586
+        }
1587
+
1588
+        /** @var ConfigLexiconEntry $configValue */
1589
+        $configValue = $configDetails['entries'][$key];
1590
+        $type &= ~self::VALUE_SENSITIVE;
1591
+
1592
+        $appConfigValueType = $configValue->getValueType()->toAppConfigFlag();
1593
+        if ($type === self::VALUE_MIXED) {
1594
+            $type = $appConfigValueType; // we overwrite if value was requested as mixed
1595
+        } elseif ($appConfigValueType !== $type) {
1596
+            throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1597
+        }
1598
+
1599
+        $lazy = $configValue->isLazy();
1600
+        $default = $configValue->getDefault() ?? $default; // default from Lexicon got priority
1601
+        if ($configValue->isFlagged(self::FLAG_SENSITIVE)) {
1602
+            $type |= self::VALUE_SENSITIVE;
1603
+        }
1604
+        if ($configValue->isDeprecated()) {
1605
+            $this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.');
1606
+        }
1607
+
1608
+        return true;
1609
+    }
1610
+
1611
+    /**
1612
+     * manage ConfigLexicon behavior based on strictness set in IConfigLexicon
1613
+     *
1614
+     * @param ConfigLexiconStrictness|null $strictness
1615
+     * @param string $line
1616
+     *
1617
+     * @return bool TRUE if conflict can be fully ignored, FALSE if action should be not performed
1618
+     * @throws AppConfigUnknownKeyException if strictness implies exception
1619
+     * @see IConfigLexicon::getStrictness()
1620
+     */
1621
+    private function applyLexiconStrictness(
1622
+        ?ConfigLexiconStrictness $strictness,
1623
+        string $line = '',
1624
+    ): bool {
1625
+        if ($strictness === null) {
1626
+            return true;
1627
+        }
1628
+
1629
+        switch ($strictness) {
1630
+            case ConfigLexiconStrictness::IGNORE:
1631
+                return true;
1632
+            case ConfigLexiconStrictness::NOTICE:
1633
+                $this->logger->notice($line);
1634
+                return true;
1635
+            case ConfigLexiconStrictness::WARNING:
1636
+                $this->logger->warning($line);
1637
+                return false;
1638
+        }
1639
+
1640
+        throw new AppConfigUnknownKeyException($line);
1641
+    }
1642
+
1643
+    /**
1644
+     * extract details from registered $appId's config lexicon
1645
+     *
1646
+     * @param string $appId
1647
+     *
1648
+     * @return array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}
1649
+     */
1650
+    private function getConfigDetailsFromLexicon(string $appId): array {
1651
+        if (!array_key_exists($appId, $this->configLexiconDetails)) {
1652
+            $entries = [];
1653
+            $bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
1654
+            $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
1655
+            foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
1656
+                $entries[$configEntry->getKey()] = $configEntry;
1657
+            }
1658
+
1659
+            $this->configLexiconDetails[$appId] = [
1660
+                'entries' => $entries,
1661
+                'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE
1662
+            ];
1663
+        }
1664
+
1665
+        return $this->configLexiconDetails[$appId];
1666
+    }
1667
+
1668
+    /**
1669
+     * Returns the installed versions of all apps
1670
+     *
1671
+     * @return array<string, string>
1672
+     */
1673
+    public function getAppInstalledVersions(bool $onlyEnabled = false): array {
1674
+        if ($this->appVersionsCache === null) {
1675
+            /** @var array<string, string> */
1676
+            $this->appVersionsCache = $this->searchValues('installed_version', false, IAppConfig::VALUE_STRING);
1677
+        }
1678
+        if ($onlyEnabled) {
1679
+            return array_filter(
1680
+                $this->appVersionsCache,
1681
+                fn (string $app): bool => $this->getValueString($app, 'enabled', 'no') !== 'no',
1682
+                ARRAY_FILTER_USE_KEY
1683
+            );
1684
+        }
1685
+        return $this->appVersionsCache;
1686
+    }
1687 1687
 }
Please login to merge, or discard this patch.
lib/private/Route/Router.php 2 patches
Indentation   +518 added lines, -518 removed lines patch added patch discarded remove patch
@@ -31,527 +31,527 @@
 block discarded – undo
31 31
 use Symfony\Component\Routing\RouteCollection;
32 32
 
33 33
 class Router implements IRouter {
34
-	/** @var RouteCollection[] */
35
-	protected $collections = [];
36
-	/** @var null|RouteCollection */
37
-	protected $collection = null;
38
-	/** @var null|string */
39
-	protected $collectionName = null;
40
-	/** @var null|RouteCollection */
41
-	protected $root = null;
42
-	/** @var null|UrlGenerator */
43
-	protected $generator = null;
44
-	/** @var string[]|null */
45
-	protected $routingFiles;
46
-	/** @var bool */
47
-	protected $loaded = false;
48
-	/** @var array */
49
-	protected $loadedApps = [];
50
-	/** @var RequestContext */
51
-	protected $context;
52
-
53
-	public function __construct(
54
-		protected LoggerInterface $logger,
55
-		IRequest $request,
56
-		private IConfig $config,
57
-		protected IEventLogger $eventLogger,
58
-		private ContainerInterface $container,
59
-		protected IAppManager $appManager,
60
-	) {
61
-		$baseUrl = \OC::$WEBROOT;
62
-		if (!($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) {
63
-			$baseUrl .= '/index.php';
64
-		}
65
-		if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) {
66
-			$method = $_SERVER['REQUEST_METHOD'];
67
-		} else {
68
-			$method = 'GET';
69
-		}
70
-		$host = $request->getServerHost();
71
-		$schema = $request->getServerProtocol();
72
-		$this->context = new RequestContext($baseUrl, $method, $host, $schema);
73
-		// TODO cache
74
-		$this->root = $this->getCollection('root');
75
-	}
76
-
77
-	/**
78
-	 * Get the files to load the routes from
79
-	 *
80
-	 * @return string[]
81
-	 */
82
-	public function getRoutingFiles() {
83
-		if ($this->routingFiles === null) {
84
-			$this->routingFiles = [];
85
-			foreach ($this->appManager->getEnabledApps() as $app) {
86
-				try {
87
-					$appPath = $this->appManager->getAppPath($app);
88
-					$file = $appPath . '/appinfo/routes.php';
89
-					if (file_exists($file)) {
90
-						$this->routingFiles[$app] = $file;
91
-					}
92
-				} catch (AppPathNotFoundException) {
93
-					/* ignore */
94
-				}
95
-			}
96
-		}
97
-		return $this->routingFiles;
98
-	}
99
-
100
-	/**
101
-	 * Loads the routes
102
-	 *
103
-	 * @param null|string $app
104
-	 */
105
-	public function loadRoutes($app = null) {
106
-		if (is_string($app)) {
107
-			$app = $this->appManager->cleanAppId($app);
108
-		}
109
-
110
-		$requestedApp = $app;
111
-		if ($this->loaded) {
112
-			return;
113
-		}
114
-		$this->eventLogger->start('route:load:' . $requestedApp, 'Loading Routes for ' . $requestedApp);
115
-		if (is_null($app)) {
116
-			$this->loaded = true;
117
-			$routingFiles = $this->getRoutingFiles();
118
-
119
-			$this->eventLogger->start('route:load:attributes', 'Loading Routes from attributes');
120
-			foreach ($this->appManager->getEnabledApps() as $enabledApp) {
121
-				$this->loadAttributeRoutes($enabledApp);
122
-			}
123
-			$this->eventLogger->end('route:load:attributes');
124
-		} else {
125
-			if (isset($this->loadedApps[$app])) {
126
-				return;
127
-			}
128
-			try {
129
-				$appPath = $this->appManager->getAppPath($app);
130
-				$file = $appPath . '/appinfo/routes.php';
131
-				if (file_exists($file)) {
132
-					$routingFiles = [$app => $file];
133
-				} else {
134
-					$routingFiles = [];
135
-				}
136
-			} catch (AppPathNotFoundException) {
137
-				$routingFiles = [];
138
-			}
139
-
140
-			if ($this->appManager->isEnabledForUser($app)) {
141
-				$this->loadAttributeRoutes($app);
142
-			}
143
-		}
144
-
145
-		$this->eventLogger->start('route:load:files', 'Loading Routes from files');
146
-		foreach ($routingFiles as $app => $file) {
147
-			if (!isset($this->loadedApps[$app])) {
148
-				if (!$this->appManager->isAppLoaded($app)) {
149
-					// app MUST be loaded before app routes
150
-					// try again next time loadRoutes() is called
151
-					$this->loaded = false;
152
-					continue;
153
-				}
154
-				$this->loadedApps[$app] = true;
155
-				$this->useCollection($app);
156
-				$this->requireRouteFile($file, $app);
157
-				$collection = $this->getCollection($app);
158
-				$this->root->addCollection($collection);
159
-
160
-				// Also add the OCS collection
161
-				$collection = $this->getCollection($app . '.ocs');
162
-				$collection->addPrefix('/ocsapp');
163
-				$this->root->addCollection($collection);
164
-			}
165
-		}
166
-		$this->eventLogger->end('route:load:files');
167
-
168
-		if (!isset($this->loadedApps['core'])) {
169
-			$this->loadedApps['core'] = true;
170
-			$this->useCollection('root');
171
-			$this->setupRoutes($this->getAttributeRoutes('core'), 'core');
172
-			$this->requireRouteFile(__DIR__ . '/../../../core/routes.php', 'core');
173
-
174
-			// Also add the OCS collection
175
-			$collection = $this->getCollection('root.ocs');
176
-			$collection->addPrefix('/ocsapp');
177
-			$this->root->addCollection($collection);
178
-		}
179
-		if ($this->loaded) {
180
-			$collection = $this->getCollection('ocs');
181
-			$collection->addPrefix('/ocs');
182
-			$this->root->addCollection($collection);
183
-		}
184
-		$this->eventLogger->end('route:load:' . $requestedApp);
185
-	}
186
-
187
-	/**
188
-	 * @param string $name
189
-	 * @return \Symfony\Component\Routing\RouteCollection
190
-	 */
191
-	protected function getCollection($name) {
192
-		if (!isset($this->collections[$name])) {
193
-			$this->collections[$name] = new RouteCollection();
194
-		}
195
-		return $this->collections[$name];
196
-	}
197
-
198
-	/**
199
-	 * Sets the collection to use for adding routes
200
-	 *
201
-	 * @param string $name Name of the collection to use.
202
-	 * @return void
203
-	 */
204
-	public function useCollection($name) {
205
-		$this->collection = $this->getCollection($name);
206
-		$this->collectionName = $name;
207
-	}
208
-
209
-	/**
210
-	 * returns the current collection name in use for adding routes
211
-	 *
212
-	 * @return string the collection name
213
-	 */
214
-	public function getCurrentCollection() {
215
-		return $this->collectionName;
216
-	}
217
-
218
-
219
-	/**
220
-	 * Create a \OC\Route\Route.
221
-	 *
222
-	 * @param string $name Name of the route to create.
223
-	 * @param string $pattern The pattern to match
224
-	 * @param array $defaults An array of default parameter values
225
-	 * @param array $requirements An array of requirements for parameters (regexes)
226
-	 * @return \OC\Route\Route
227
-	 */
228
-	public function create($name,
229
-		$pattern,
230
-		array $defaults = [],
231
-		array $requirements = []) {
232
-		$route = new Route($pattern, $defaults, $requirements);
233
-		$this->collection->add($name, $route);
234
-		return $route;
235
-	}
236
-
237
-	/**
238
-	 * Find the route matching $url
239
-	 *
240
-	 * @param string $url The url to find
241
-	 * @throws \Exception
242
-	 * @return array
243
-	 */
244
-	public function findMatchingRoute(string $url): array {
245
-		$this->eventLogger->start('route:match', 'Match route');
246
-		if (str_starts_with($url, '/apps/')) {
247
-			// empty string / 'apps' / $app / rest of the route
248
-			[, , $app,] = explode('/', $url, 4);
249
-
250
-			$app = $this->appManager->cleanAppId($app);
251
-			\OC::$REQUESTEDAPP = $app;
252
-			$this->loadRoutes($app);
253
-		} elseif (str_starts_with($url, '/ocsapp/apps/')) {
254
-			// empty string / 'ocsapp' / 'apps' / $app / rest of the route
255
-			[, , , $app,] = explode('/', $url, 5);
256
-
257
-			$app = $this->appManager->cleanAppId($app);
258
-			\OC::$REQUESTEDAPP = $app;
259
-			$this->loadRoutes($app);
260
-		} elseif (str_starts_with($url, '/settings/')) {
261
-			$this->loadRoutes('settings');
262
-		} elseif (str_starts_with($url, '/core/')) {
263
-			\OC::$REQUESTEDAPP = $url;
264
-			if ($this->config->getSystemValueBool('installed', false) && !Util::needUpgrade()) {
265
-				$this->appManager->loadApps();
266
-			}
267
-			$this->loadRoutes('core');
268
-		} else {
269
-			$this->loadRoutes();
270
-		}
271
-
272
-		$this->eventLogger->start('route:url:match', 'Symfony url matcher call');
273
-		$matcher = new UrlMatcher($this->root, $this->context);
274
-		try {
275
-			$parameters = $matcher->match($url);
276
-		} catch (ResourceNotFoundException $e) {
277
-			if (!str_ends_with($url, '/')) {
278
-				// We allow links to apps/files? for backwards compatibility reasons
279
-				// However, since Symfony does not allow empty route names, the route
280
-				// we need to match is '/', so we need to append the '/' here.
281
-				try {
282
-					$parameters = $matcher->match($url . '/');
283
-				} catch (ResourceNotFoundException $newException) {
284
-					// If we still didn't match a route, we throw the original exception
285
-					throw $e;
286
-				}
287
-			} else {
288
-				throw $e;
289
-			}
290
-		}
291
-		$this->eventLogger->end('route:url:match');
292
-
293
-		$this->eventLogger->end('route:match');
294
-		return $parameters;
295
-	}
296
-
297
-	/**
298
-	 * Find and execute the route matching $url
299
-	 *
300
-	 * @param string $url The url to find
301
-	 * @throws \Exception
302
-	 * @return void
303
-	 */
304
-	public function match($url) {
305
-		$parameters = $this->findMatchingRoute($url);
306
-
307
-		$this->eventLogger->start('route:run', 'Run route');
308
-		if (isset($parameters['caller'])) {
309
-			$caller = $parameters['caller'];
310
-			unset($parameters['caller']);
311
-			unset($parameters['action']);
312
-			$application = $this->getApplicationClass($caller[0]);
313
-			\OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters);
314
-		} elseif (isset($parameters['action'])) {
315
-			$this->logger->warning('Deprecated action route used', ['parameters' => $parameters]);
316
-			$this->callLegacyActionRoute($parameters);
317
-		} elseif (isset($parameters['file'])) {
318
-			$this->logger->debug('Deprecated file route used', ['parameters' => $parameters]);
319
-			$this->includeLegacyFileRoute($parameters);
320
-		} else {
321
-			throw new \Exception('no action available');
322
-		}
323
-		$this->eventLogger->end('route:run');
324
-	}
325
-
326
-	/**
327
-	 * @param array{file:mixed, ...} $parameters
328
-	 */
329
-	protected function includeLegacyFileRoute(array $parameters): void {
330
-		$param = $parameters;
331
-		unset($param['_route']);
332
-		$_GET = array_merge($_GET, $param);
333
-		unset($param);
334
-		require_once $parameters['file'];
335
-	}
336
-
337
-	/**
338
-	 * @param array{action:mixed, ...} $parameters
339
-	 */
340
-	protected function callLegacyActionRoute(array $parameters): void {
341
-		$action = $parameters['action'];
342
-		if (!is_callable($action)) {
343
-			throw new \Exception('not a callable action');
344
-		}
345
-		unset($parameters['action']);
346
-		unset($parameters['caller']);
347
-		$this->eventLogger->start('route:run:call', 'Run callable route');
348
-		call_user_func($action, $parameters);
349
-		$this->eventLogger->end('route:run:call');
350
-	}
351
-
352
-	/**
353
-	 * Get the url generator
354
-	 *
355
-	 * @return \Symfony\Component\Routing\Generator\UrlGenerator
356
-	 *
357
-	 */
358
-	public function getGenerator() {
359
-		if ($this->generator !== null) {
360
-			return $this->generator;
361
-		}
362
-
363
-		return $this->generator = new UrlGenerator($this->root, $this->context);
364
-	}
365
-
366
-	/**
367
-	 * Generate url based on $name and $parameters
368
-	 *
369
-	 * @param string $name Name of the route to use.
370
-	 * @param array $parameters Parameters for the route
371
-	 * @param bool $absolute
372
-	 * @return string
373
-	 */
374
-	public function generate($name,
375
-		$parameters = [],
376
-		$absolute = false) {
377
-		$referenceType = UrlGenerator::ABSOLUTE_URL;
378
-		if ($absolute === false) {
379
-			$referenceType = UrlGenerator::ABSOLUTE_PATH;
380
-		}
381
-		/*
34
+    /** @var RouteCollection[] */
35
+    protected $collections = [];
36
+    /** @var null|RouteCollection */
37
+    protected $collection = null;
38
+    /** @var null|string */
39
+    protected $collectionName = null;
40
+    /** @var null|RouteCollection */
41
+    protected $root = null;
42
+    /** @var null|UrlGenerator */
43
+    protected $generator = null;
44
+    /** @var string[]|null */
45
+    protected $routingFiles;
46
+    /** @var bool */
47
+    protected $loaded = false;
48
+    /** @var array */
49
+    protected $loadedApps = [];
50
+    /** @var RequestContext */
51
+    protected $context;
52
+
53
+    public function __construct(
54
+        protected LoggerInterface $logger,
55
+        IRequest $request,
56
+        private IConfig $config,
57
+        protected IEventLogger $eventLogger,
58
+        private ContainerInterface $container,
59
+        protected IAppManager $appManager,
60
+    ) {
61
+        $baseUrl = \OC::$WEBROOT;
62
+        if (!($config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true')) {
63
+            $baseUrl .= '/index.php';
64
+        }
65
+        if (!\OC::$CLI && isset($_SERVER['REQUEST_METHOD'])) {
66
+            $method = $_SERVER['REQUEST_METHOD'];
67
+        } else {
68
+            $method = 'GET';
69
+        }
70
+        $host = $request->getServerHost();
71
+        $schema = $request->getServerProtocol();
72
+        $this->context = new RequestContext($baseUrl, $method, $host, $schema);
73
+        // TODO cache
74
+        $this->root = $this->getCollection('root');
75
+    }
76
+
77
+    /**
78
+     * Get the files to load the routes from
79
+     *
80
+     * @return string[]
81
+     */
82
+    public function getRoutingFiles() {
83
+        if ($this->routingFiles === null) {
84
+            $this->routingFiles = [];
85
+            foreach ($this->appManager->getEnabledApps() as $app) {
86
+                try {
87
+                    $appPath = $this->appManager->getAppPath($app);
88
+                    $file = $appPath . '/appinfo/routes.php';
89
+                    if (file_exists($file)) {
90
+                        $this->routingFiles[$app] = $file;
91
+                    }
92
+                } catch (AppPathNotFoundException) {
93
+                    /* ignore */
94
+                }
95
+            }
96
+        }
97
+        return $this->routingFiles;
98
+    }
99
+
100
+    /**
101
+     * Loads the routes
102
+     *
103
+     * @param null|string $app
104
+     */
105
+    public function loadRoutes($app = null) {
106
+        if (is_string($app)) {
107
+            $app = $this->appManager->cleanAppId($app);
108
+        }
109
+
110
+        $requestedApp = $app;
111
+        if ($this->loaded) {
112
+            return;
113
+        }
114
+        $this->eventLogger->start('route:load:' . $requestedApp, 'Loading Routes for ' . $requestedApp);
115
+        if (is_null($app)) {
116
+            $this->loaded = true;
117
+            $routingFiles = $this->getRoutingFiles();
118
+
119
+            $this->eventLogger->start('route:load:attributes', 'Loading Routes from attributes');
120
+            foreach ($this->appManager->getEnabledApps() as $enabledApp) {
121
+                $this->loadAttributeRoutes($enabledApp);
122
+            }
123
+            $this->eventLogger->end('route:load:attributes');
124
+        } else {
125
+            if (isset($this->loadedApps[$app])) {
126
+                return;
127
+            }
128
+            try {
129
+                $appPath = $this->appManager->getAppPath($app);
130
+                $file = $appPath . '/appinfo/routes.php';
131
+                if (file_exists($file)) {
132
+                    $routingFiles = [$app => $file];
133
+                } else {
134
+                    $routingFiles = [];
135
+                }
136
+            } catch (AppPathNotFoundException) {
137
+                $routingFiles = [];
138
+            }
139
+
140
+            if ($this->appManager->isEnabledForUser($app)) {
141
+                $this->loadAttributeRoutes($app);
142
+            }
143
+        }
144
+
145
+        $this->eventLogger->start('route:load:files', 'Loading Routes from files');
146
+        foreach ($routingFiles as $app => $file) {
147
+            if (!isset($this->loadedApps[$app])) {
148
+                if (!$this->appManager->isAppLoaded($app)) {
149
+                    // app MUST be loaded before app routes
150
+                    // try again next time loadRoutes() is called
151
+                    $this->loaded = false;
152
+                    continue;
153
+                }
154
+                $this->loadedApps[$app] = true;
155
+                $this->useCollection($app);
156
+                $this->requireRouteFile($file, $app);
157
+                $collection = $this->getCollection($app);
158
+                $this->root->addCollection($collection);
159
+
160
+                // Also add the OCS collection
161
+                $collection = $this->getCollection($app . '.ocs');
162
+                $collection->addPrefix('/ocsapp');
163
+                $this->root->addCollection($collection);
164
+            }
165
+        }
166
+        $this->eventLogger->end('route:load:files');
167
+
168
+        if (!isset($this->loadedApps['core'])) {
169
+            $this->loadedApps['core'] = true;
170
+            $this->useCollection('root');
171
+            $this->setupRoutes($this->getAttributeRoutes('core'), 'core');
172
+            $this->requireRouteFile(__DIR__ . '/../../../core/routes.php', 'core');
173
+
174
+            // Also add the OCS collection
175
+            $collection = $this->getCollection('root.ocs');
176
+            $collection->addPrefix('/ocsapp');
177
+            $this->root->addCollection($collection);
178
+        }
179
+        if ($this->loaded) {
180
+            $collection = $this->getCollection('ocs');
181
+            $collection->addPrefix('/ocs');
182
+            $this->root->addCollection($collection);
183
+        }
184
+        $this->eventLogger->end('route:load:' . $requestedApp);
185
+    }
186
+
187
+    /**
188
+     * @param string $name
189
+     * @return \Symfony\Component\Routing\RouteCollection
190
+     */
191
+    protected function getCollection($name) {
192
+        if (!isset($this->collections[$name])) {
193
+            $this->collections[$name] = new RouteCollection();
194
+        }
195
+        return $this->collections[$name];
196
+    }
197
+
198
+    /**
199
+     * Sets the collection to use for adding routes
200
+     *
201
+     * @param string $name Name of the collection to use.
202
+     * @return void
203
+     */
204
+    public function useCollection($name) {
205
+        $this->collection = $this->getCollection($name);
206
+        $this->collectionName = $name;
207
+    }
208
+
209
+    /**
210
+     * returns the current collection name in use for adding routes
211
+     *
212
+     * @return string the collection name
213
+     */
214
+    public function getCurrentCollection() {
215
+        return $this->collectionName;
216
+    }
217
+
218
+
219
+    /**
220
+     * Create a \OC\Route\Route.
221
+     *
222
+     * @param string $name Name of the route to create.
223
+     * @param string $pattern The pattern to match
224
+     * @param array $defaults An array of default parameter values
225
+     * @param array $requirements An array of requirements for parameters (regexes)
226
+     * @return \OC\Route\Route
227
+     */
228
+    public function create($name,
229
+        $pattern,
230
+        array $defaults = [],
231
+        array $requirements = []) {
232
+        $route = new Route($pattern, $defaults, $requirements);
233
+        $this->collection->add($name, $route);
234
+        return $route;
235
+    }
236
+
237
+    /**
238
+     * Find the route matching $url
239
+     *
240
+     * @param string $url The url to find
241
+     * @throws \Exception
242
+     * @return array
243
+     */
244
+    public function findMatchingRoute(string $url): array {
245
+        $this->eventLogger->start('route:match', 'Match route');
246
+        if (str_starts_with($url, '/apps/')) {
247
+            // empty string / 'apps' / $app / rest of the route
248
+            [, , $app,] = explode('/', $url, 4);
249
+
250
+            $app = $this->appManager->cleanAppId($app);
251
+            \OC::$REQUESTEDAPP = $app;
252
+            $this->loadRoutes($app);
253
+        } elseif (str_starts_with($url, '/ocsapp/apps/')) {
254
+            // empty string / 'ocsapp' / 'apps' / $app / rest of the route
255
+            [, , , $app,] = explode('/', $url, 5);
256
+
257
+            $app = $this->appManager->cleanAppId($app);
258
+            \OC::$REQUESTEDAPP = $app;
259
+            $this->loadRoutes($app);
260
+        } elseif (str_starts_with($url, '/settings/')) {
261
+            $this->loadRoutes('settings');
262
+        } elseif (str_starts_with($url, '/core/')) {
263
+            \OC::$REQUESTEDAPP = $url;
264
+            if ($this->config->getSystemValueBool('installed', false) && !Util::needUpgrade()) {
265
+                $this->appManager->loadApps();
266
+            }
267
+            $this->loadRoutes('core');
268
+        } else {
269
+            $this->loadRoutes();
270
+        }
271
+
272
+        $this->eventLogger->start('route:url:match', 'Symfony url matcher call');
273
+        $matcher = new UrlMatcher($this->root, $this->context);
274
+        try {
275
+            $parameters = $matcher->match($url);
276
+        } catch (ResourceNotFoundException $e) {
277
+            if (!str_ends_with($url, '/')) {
278
+                // We allow links to apps/files? for backwards compatibility reasons
279
+                // However, since Symfony does not allow empty route names, the route
280
+                // we need to match is '/', so we need to append the '/' here.
281
+                try {
282
+                    $parameters = $matcher->match($url . '/');
283
+                } catch (ResourceNotFoundException $newException) {
284
+                    // If we still didn't match a route, we throw the original exception
285
+                    throw $e;
286
+                }
287
+            } else {
288
+                throw $e;
289
+            }
290
+        }
291
+        $this->eventLogger->end('route:url:match');
292
+
293
+        $this->eventLogger->end('route:match');
294
+        return $parameters;
295
+    }
296
+
297
+    /**
298
+     * Find and execute the route matching $url
299
+     *
300
+     * @param string $url The url to find
301
+     * @throws \Exception
302
+     * @return void
303
+     */
304
+    public function match($url) {
305
+        $parameters = $this->findMatchingRoute($url);
306
+
307
+        $this->eventLogger->start('route:run', 'Run route');
308
+        if (isset($parameters['caller'])) {
309
+            $caller = $parameters['caller'];
310
+            unset($parameters['caller']);
311
+            unset($parameters['action']);
312
+            $application = $this->getApplicationClass($caller[0]);
313
+            \OC\AppFramework\App::main($caller[1], $caller[2], $application->getContainer(), $parameters);
314
+        } elseif (isset($parameters['action'])) {
315
+            $this->logger->warning('Deprecated action route used', ['parameters' => $parameters]);
316
+            $this->callLegacyActionRoute($parameters);
317
+        } elseif (isset($parameters['file'])) {
318
+            $this->logger->debug('Deprecated file route used', ['parameters' => $parameters]);
319
+            $this->includeLegacyFileRoute($parameters);
320
+        } else {
321
+            throw new \Exception('no action available');
322
+        }
323
+        $this->eventLogger->end('route:run');
324
+    }
325
+
326
+    /**
327
+     * @param array{file:mixed, ...} $parameters
328
+     */
329
+    protected function includeLegacyFileRoute(array $parameters): void {
330
+        $param = $parameters;
331
+        unset($param['_route']);
332
+        $_GET = array_merge($_GET, $param);
333
+        unset($param);
334
+        require_once $parameters['file'];
335
+    }
336
+
337
+    /**
338
+     * @param array{action:mixed, ...} $parameters
339
+     */
340
+    protected function callLegacyActionRoute(array $parameters): void {
341
+        $action = $parameters['action'];
342
+        if (!is_callable($action)) {
343
+            throw new \Exception('not a callable action');
344
+        }
345
+        unset($parameters['action']);
346
+        unset($parameters['caller']);
347
+        $this->eventLogger->start('route:run:call', 'Run callable route');
348
+        call_user_func($action, $parameters);
349
+        $this->eventLogger->end('route:run:call');
350
+    }
351
+
352
+    /**
353
+     * Get the url generator
354
+     *
355
+     * @return \Symfony\Component\Routing\Generator\UrlGenerator
356
+     *
357
+     */
358
+    public function getGenerator() {
359
+        if ($this->generator !== null) {
360
+            return $this->generator;
361
+        }
362
+
363
+        return $this->generator = new UrlGenerator($this->root, $this->context);
364
+    }
365
+
366
+    /**
367
+     * Generate url based on $name and $parameters
368
+     *
369
+     * @param string $name Name of the route to use.
370
+     * @param array $parameters Parameters for the route
371
+     * @param bool $absolute
372
+     * @return string
373
+     */
374
+    public function generate($name,
375
+        $parameters = [],
376
+        $absolute = false) {
377
+        $referenceType = UrlGenerator::ABSOLUTE_URL;
378
+        if ($absolute === false) {
379
+            $referenceType = UrlGenerator::ABSOLUTE_PATH;
380
+        }
381
+        /*
382 382
 		 * The route name has to be lowercase, for symfony to match it correctly.
383 383
 		 * This is required because smyfony allows mixed casing for controller names in the routes.
384 384
 		 * To avoid breaking all the existing route names, registering and matching will only use the lowercase names.
385 385
 		 * This is also safe on the PHP side because class and method names collide regardless of the casing.
386 386
 		 */
387
-		$name = strtolower($name);
388
-		$name = $this->fixLegacyRootName($name);
389
-		if (str_contains($name, '.')) {
390
-			[$appName, $other] = explode('.', $name, 3);
391
-			// OCS routes are prefixed with "ocs."
392
-			if ($appName === 'ocs') {
393
-				$appName = $other;
394
-			}
395
-			$this->loadRoutes($appName);
396
-			try {
397
-				return $this->getGenerator()->generate($name, $parameters, $referenceType);
398
-			} catch (RouteNotFoundException $e) {
399
-			}
400
-		}
401
-
402
-		// Fallback load all routes
403
-		$this->loadRoutes();
404
-		try {
405
-			return $this->getGenerator()->generate($name, $parameters, $referenceType);
406
-		} catch (RouteNotFoundException $e) {
407
-			$this->logger->info($e->getMessage(), ['exception' => $e]);
408
-			return '';
409
-		}
410
-	}
411
-
412
-	protected function fixLegacyRootName(string $routeName): string {
413
-		if ($routeName === 'files.viewcontroller.showfile') {
414
-			return 'files.view.showfile';
415
-		}
416
-		if ($routeName === 'files_sharing.sharecontroller.showshare') {
417
-			return 'files_sharing.share.showshare';
418
-		}
419
-		if ($routeName === 'files_sharing.sharecontroller.showauthenticate') {
420
-			return 'files_sharing.share.showauthenticate';
421
-		}
422
-		if ($routeName === 'files_sharing.sharecontroller.authenticate') {
423
-			return 'files_sharing.share.authenticate';
424
-		}
425
-		if ($routeName === 'files_sharing.sharecontroller.downloadshare') {
426
-			return 'files_sharing.share.downloadshare';
427
-		}
428
-		if ($routeName === 'files_sharing.publicpreview.directlink') {
429
-			return 'files_sharing.publicpreview.directlink';
430
-		}
431
-		if ($routeName === 'cloud_federation_api.requesthandlercontroller.addshare') {
432
-			return 'cloud_federation_api.requesthandler.addshare';
433
-		}
434
-		if ($routeName === 'cloud_federation_api.requesthandlercontroller.receivenotification') {
435
-			return 'cloud_federation_api.requesthandler.receivenotification';
436
-		}
437
-		if ($routeName === 'core.ProfilePage.index') {
438
-			return 'profile.ProfilePage.index';
439
-		}
440
-		return $routeName;
441
-	}
442
-
443
-	private function loadAttributeRoutes(string $app): void {
444
-		$routes = $this->getAttributeRoutes($app);
445
-		if (count($routes) === 0) {
446
-			return;
447
-		}
448
-
449
-		$this->useCollection($app);
450
-		$this->setupRoutes($routes, $app);
451
-		$collection = $this->getCollection($app);
452
-		$this->root->addCollection($collection);
453
-
454
-		// Also add the OCS collection
455
-		$collection = $this->getCollection($app . '.ocs');
456
-		$collection->addPrefix('/ocsapp');
457
-		$this->root->addCollection($collection);
458
-	}
459
-
460
-	/**
461
-	 * @throws ReflectionException
462
-	 */
463
-	private function getAttributeRoutes(string $app): array {
464
-		$routes = [];
465
-
466
-		if ($app === 'core') {
467
-			$appControllerPath = __DIR__ . '/../../../core/Controller';
468
-			$appNameSpace = 'OC\\Core';
469
-		} else {
470
-			try {
471
-				$appControllerPath = $this->appManager->getAppPath($app) . '/lib/Controller';
472
-			} catch (AppPathNotFoundException) {
473
-				return [];
474
-			}
475
-			$appNameSpace = App::buildAppNamespace($app);
476
-		}
477
-
478
-		if (!file_exists($appControllerPath)) {
479
-			return [];
480
-		}
481
-
482
-		$dir = new DirectoryIterator($appControllerPath);
483
-		foreach ($dir as $file) {
484
-			if (!str_ends_with($file->getPathname(), 'Controller.php')) {
485
-				continue;
486
-			}
487
-
488
-			$class = new ReflectionClass($appNameSpace . '\\Controller\\' . basename($file->getPathname(), '.php'));
489
-
490
-			foreach ($class->getMethods() as $method) {
491
-				foreach ($method->getAttributes(RouteAttribute::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
492
-					$route = $attribute->newInstance();
493
-
494
-					$serializedRoute = $route->toArray();
495
-					// Remove 'Controller' suffix
496
-					$serializedRoute['name'] = substr($class->getShortName(), 0, -10) . '#' . $method->getName();
497
-
498
-					$key = $route->getType();
499
-
500
-					$routes[$key] ??= [];
501
-					$routes[$key][] = $serializedRoute;
502
-				}
503
-			}
504
-		}
505
-
506
-		return $routes;
507
-	}
508
-
509
-	/**
510
-	 * To isolate the variable scope used inside the $file it is required in it's own method
511
-	 *
512
-	 * @param string $file the route file location to include
513
-	 * @param string $appName
514
-	 */
515
-	protected function requireRouteFile(string $file, string $appName): void {
516
-		$this->setupRoutes(include $file, $appName);
517
-	}
518
-
519
-
520
-	/**
521
-	 * If a routes.php file returns an array, try to set up the application and
522
-	 * register the routes for the app. The application class will be chosen by
523
-	 * camelcasing the appname, e.g.: my_app will be turned into
524
-	 * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
525
-	 * App will be initialized. This makes it optional to ship an
526
-	 * appinfo/application.php by using the built in query resolver
527
-	 *
528
-	 * @param array $routes the application routes
529
-	 * @param string $appName the name of the app.
530
-	 */
531
-	private function setupRoutes($routes, $appName) {
532
-		if (is_array($routes)) {
533
-			$routeParser = new RouteParser();
534
-
535
-			$defaultRoutes = $routeParser->parseDefaultRoutes($routes, $appName);
536
-			$ocsRoutes = $routeParser->parseOCSRoutes($routes, $appName);
537
-
538
-			$this->root->addCollection($defaultRoutes);
539
-			$ocsRoutes->addPrefix('/ocsapp');
540
-			$this->root->addCollection($ocsRoutes);
541
-		}
542
-	}
543
-
544
-	private function getApplicationClass(string $appName) {
545
-		$appNameSpace = App::buildAppNamespace($appName);
546
-
547
-		$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
548
-
549
-		if (class_exists($applicationClassName)) {
550
-			$application = $this->container->get($applicationClassName);
551
-		} else {
552
-			$application = new App($appName);
553
-		}
554
-
555
-		return $application;
556
-	}
387
+        $name = strtolower($name);
388
+        $name = $this->fixLegacyRootName($name);
389
+        if (str_contains($name, '.')) {
390
+            [$appName, $other] = explode('.', $name, 3);
391
+            // OCS routes are prefixed with "ocs."
392
+            if ($appName === 'ocs') {
393
+                $appName = $other;
394
+            }
395
+            $this->loadRoutes($appName);
396
+            try {
397
+                return $this->getGenerator()->generate($name, $parameters, $referenceType);
398
+            } catch (RouteNotFoundException $e) {
399
+            }
400
+        }
401
+
402
+        // Fallback load all routes
403
+        $this->loadRoutes();
404
+        try {
405
+            return $this->getGenerator()->generate($name, $parameters, $referenceType);
406
+        } catch (RouteNotFoundException $e) {
407
+            $this->logger->info($e->getMessage(), ['exception' => $e]);
408
+            return '';
409
+        }
410
+    }
411
+
412
+    protected function fixLegacyRootName(string $routeName): string {
413
+        if ($routeName === 'files.viewcontroller.showfile') {
414
+            return 'files.view.showfile';
415
+        }
416
+        if ($routeName === 'files_sharing.sharecontroller.showshare') {
417
+            return 'files_sharing.share.showshare';
418
+        }
419
+        if ($routeName === 'files_sharing.sharecontroller.showauthenticate') {
420
+            return 'files_sharing.share.showauthenticate';
421
+        }
422
+        if ($routeName === 'files_sharing.sharecontroller.authenticate') {
423
+            return 'files_sharing.share.authenticate';
424
+        }
425
+        if ($routeName === 'files_sharing.sharecontroller.downloadshare') {
426
+            return 'files_sharing.share.downloadshare';
427
+        }
428
+        if ($routeName === 'files_sharing.publicpreview.directlink') {
429
+            return 'files_sharing.publicpreview.directlink';
430
+        }
431
+        if ($routeName === 'cloud_federation_api.requesthandlercontroller.addshare') {
432
+            return 'cloud_federation_api.requesthandler.addshare';
433
+        }
434
+        if ($routeName === 'cloud_federation_api.requesthandlercontroller.receivenotification') {
435
+            return 'cloud_federation_api.requesthandler.receivenotification';
436
+        }
437
+        if ($routeName === 'core.ProfilePage.index') {
438
+            return 'profile.ProfilePage.index';
439
+        }
440
+        return $routeName;
441
+    }
442
+
443
+    private function loadAttributeRoutes(string $app): void {
444
+        $routes = $this->getAttributeRoutes($app);
445
+        if (count($routes) === 0) {
446
+            return;
447
+        }
448
+
449
+        $this->useCollection($app);
450
+        $this->setupRoutes($routes, $app);
451
+        $collection = $this->getCollection($app);
452
+        $this->root->addCollection($collection);
453
+
454
+        // Also add the OCS collection
455
+        $collection = $this->getCollection($app . '.ocs');
456
+        $collection->addPrefix('/ocsapp');
457
+        $this->root->addCollection($collection);
458
+    }
459
+
460
+    /**
461
+     * @throws ReflectionException
462
+     */
463
+    private function getAttributeRoutes(string $app): array {
464
+        $routes = [];
465
+
466
+        if ($app === 'core') {
467
+            $appControllerPath = __DIR__ . '/../../../core/Controller';
468
+            $appNameSpace = 'OC\\Core';
469
+        } else {
470
+            try {
471
+                $appControllerPath = $this->appManager->getAppPath($app) . '/lib/Controller';
472
+            } catch (AppPathNotFoundException) {
473
+                return [];
474
+            }
475
+            $appNameSpace = App::buildAppNamespace($app);
476
+        }
477
+
478
+        if (!file_exists($appControllerPath)) {
479
+            return [];
480
+        }
481
+
482
+        $dir = new DirectoryIterator($appControllerPath);
483
+        foreach ($dir as $file) {
484
+            if (!str_ends_with($file->getPathname(), 'Controller.php')) {
485
+                continue;
486
+            }
487
+
488
+            $class = new ReflectionClass($appNameSpace . '\\Controller\\' . basename($file->getPathname(), '.php'));
489
+
490
+            foreach ($class->getMethods() as $method) {
491
+                foreach ($method->getAttributes(RouteAttribute::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
492
+                    $route = $attribute->newInstance();
493
+
494
+                    $serializedRoute = $route->toArray();
495
+                    // Remove 'Controller' suffix
496
+                    $serializedRoute['name'] = substr($class->getShortName(), 0, -10) . '#' . $method->getName();
497
+
498
+                    $key = $route->getType();
499
+
500
+                    $routes[$key] ??= [];
501
+                    $routes[$key][] = $serializedRoute;
502
+                }
503
+            }
504
+        }
505
+
506
+        return $routes;
507
+    }
508
+
509
+    /**
510
+     * To isolate the variable scope used inside the $file it is required in it's own method
511
+     *
512
+     * @param string $file the route file location to include
513
+     * @param string $appName
514
+     */
515
+    protected function requireRouteFile(string $file, string $appName): void {
516
+        $this->setupRoutes(include $file, $appName);
517
+    }
518
+
519
+
520
+    /**
521
+     * If a routes.php file returns an array, try to set up the application and
522
+     * register the routes for the app. The application class will be chosen by
523
+     * camelcasing the appname, e.g.: my_app will be turned into
524
+     * \OCA\MyApp\AppInfo\Application. If that class does not exist, a default
525
+     * App will be initialized. This makes it optional to ship an
526
+     * appinfo/application.php by using the built in query resolver
527
+     *
528
+     * @param array $routes the application routes
529
+     * @param string $appName the name of the app.
530
+     */
531
+    private function setupRoutes($routes, $appName) {
532
+        if (is_array($routes)) {
533
+            $routeParser = new RouteParser();
534
+
535
+            $defaultRoutes = $routeParser->parseDefaultRoutes($routes, $appName);
536
+            $ocsRoutes = $routeParser->parseOCSRoutes($routes, $appName);
537
+
538
+            $this->root->addCollection($defaultRoutes);
539
+            $ocsRoutes->addPrefix('/ocsapp');
540
+            $this->root->addCollection($ocsRoutes);
541
+        }
542
+    }
543
+
544
+    private function getApplicationClass(string $appName) {
545
+        $appNameSpace = App::buildAppNamespace($appName);
546
+
547
+        $applicationClassName = $appNameSpace . '\\AppInfo\\Application';
548
+
549
+        if (class_exists($applicationClassName)) {
550
+            $application = $this->container->get($applicationClassName);
551
+        } else {
552
+            $application = new App($appName);
553
+        }
554
+
555
+        return $application;
556
+    }
557 557
 }
Please login to merge, or discard this patch.
Spacing   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -85,7 +85,7 @@  discard block
 block discarded – undo
85 85
 			foreach ($this->appManager->getEnabledApps() as $app) {
86 86
 				try {
87 87
 					$appPath = $this->appManager->getAppPath($app);
88
-					$file = $appPath . '/appinfo/routes.php';
88
+					$file = $appPath.'/appinfo/routes.php';
89 89
 					if (file_exists($file)) {
90 90
 						$this->routingFiles[$app] = $file;
91 91
 					}
@@ -111,7 +111,7 @@  discard block
 block discarded – undo
111 111
 		if ($this->loaded) {
112 112
 			return;
113 113
 		}
114
-		$this->eventLogger->start('route:load:' . $requestedApp, 'Loading Routes for ' . $requestedApp);
114
+		$this->eventLogger->start('route:load:'.$requestedApp, 'Loading Routes for '.$requestedApp);
115 115
 		if (is_null($app)) {
116 116
 			$this->loaded = true;
117 117
 			$routingFiles = $this->getRoutingFiles();
@@ -127,7 +127,7 @@  discard block
 block discarded – undo
127 127
 			}
128 128
 			try {
129 129
 				$appPath = $this->appManager->getAppPath($app);
130
-				$file = $appPath . '/appinfo/routes.php';
130
+				$file = $appPath.'/appinfo/routes.php';
131 131
 				if (file_exists($file)) {
132 132
 					$routingFiles = [$app => $file];
133 133
 				} else {
@@ -158,7 +158,7 @@  discard block
 block discarded – undo
158 158
 				$this->root->addCollection($collection);
159 159
 
160 160
 				// Also add the OCS collection
161
-				$collection = $this->getCollection($app . '.ocs');
161
+				$collection = $this->getCollection($app.'.ocs');
162 162
 				$collection->addPrefix('/ocsapp');
163 163
 				$this->root->addCollection($collection);
164 164
 			}
@@ -169,7 +169,7 @@  discard block
 block discarded – undo
169 169
 			$this->loadedApps['core'] = true;
170 170
 			$this->useCollection('root');
171 171
 			$this->setupRoutes($this->getAttributeRoutes('core'), 'core');
172
-			$this->requireRouteFile(__DIR__ . '/../../../core/routes.php', 'core');
172
+			$this->requireRouteFile(__DIR__.'/../../../core/routes.php', 'core');
173 173
 
174 174
 			// Also add the OCS collection
175 175
 			$collection = $this->getCollection('root.ocs');
@@ -181,7 +181,7 @@  discard block
 block discarded – undo
181 181
 			$collection->addPrefix('/ocs');
182 182
 			$this->root->addCollection($collection);
183 183
 		}
184
-		$this->eventLogger->end('route:load:' . $requestedApp);
184
+		$this->eventLogger->end('route:load:'.$requestedApp);
185 185
 	}
186 186
 
187 187
 	/**
@@ -245,14 +245,14 @@  discard block
 block discarded – undo
245 245
 		$this->eventLogger->start('route:match', 'Match route');
246 246
 		if (str_starts_with($url, '/apps/')) {
247 247
 			// empty string / 'apps' / $app / rest of the route
248
-			[, , $app,] = explode('/', $url, 4);
248
+			[,, $app, ] = explode('/', $url, 4);
249 249
 
250 250
 			$app = $this->appManager->cleanAppId($app);
251 251
 			\OC::$REQUESTEDAPP = $app;
252 252
 			$this->loadRoutes($app);
253 253
 		} elseif (str_starts_with($url, '/ocsapp/apps/')) {
254 254
 			// empty string / 'ocsapp' / 'apps' / $app / rest of the route
255
-			[, , , $app,] = explode('/', $url, 5);
255
+			[,,, $app, ] = explode('/', $url, 5);
256 256
 
257 257
 			$app = $this->appManager->cleanAppId($app);
258 258
 			\OC::$REQUESTEDAPP = $app;
@@ -279,7 +279,7 @@  discard block
 block discarded – undo
279 279
 				// However, since Symfony does not allow empty route names, the route
280 280
 				// we need to match is '/', so we need to append the '/' here.
281 281
 				try {
282
-					$parameters = $matcher->match($url . '/');
282
+					$parameters = $matcher->match($url.'/');
283 283
 				} catch (ResourceNotFoundException $newException) {
284 284
 					// If we still didn't match a route, we throw the original exception
285 285
 					throw $e;
@@ -452,7 +452,7 @@  discard block
 block discarded – undo
452 452
 		$this->root->addCollection($collection);
453 453
 
454 454
 		// Also add the OCS collection
455
-		$collection = $this->getCollection($app . '.ocs');
455
+		$collection = $this->getCollection($app.'.ocs');
456 456
 		$collection->addPrefix('/ocsapp');
457 457
 		$this->root->addCollection($collection);
458 458
 	}
@@ -464,11 +464,11 @@  discard block
 block discarded – undo
464 464
 		$routes = [];
465 465
 
466 466
 		if ($app === 'core') {
467
-			$appControllerPath = __DIR__ . '/../../../core/Controller';
467
+			$appControllerPath = __DIR__.'/../../../core/Controller';
468 468
 			$appNameSpace = 'OC\\Core';
469 469
 		} else {
470 470
 			try {
471
-				$appControllerPath = $this->appManager->getAppPath($app) . '/lib/Controller';
471
+				$appControllerPath = $this->appManager->getAppPath($app).'/lib/Controller';
472 472
 			} catch (AppPathNotFoundException) {
473 473
 				return [];
474 474
 			}
@@ -485,7 +485,7 @@  discard block
 block discarded – undo
485 485
 				continue;
486 486
 			}
487 487
 
488
-			$class = new ReflectionClass($appNameSpace . '\\Controller\\' . basename($file->getPathname(), '.php'));
488
+			$class = new ReflectionClass($appNameSpace.'\\Controller\\'.basename($file->getPathname(), '.php'));
489 489
 
490 490
 			foreach ($class->getMethods() as $method) {
491 491
 				foreach ($method->getAttributes(RouteAttribute::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
@@ -493,7 +493,7 @@  discard block
 block discarded – undo
493 493
 
494 494
 					$serializedRoute = $route->toArray();
495 495
 					// Remove 'Controller' suffix
496
-					$serializedRoute['name'] = substr($class->getShortName(), 0, -10) . '#' . $method->getName();
496
+					$serializedRoute['name'] = substr($class->getShortName(), 0, -10).'#'.$method->getName();
497 497
 
498 498
 					$key = $route->getType();
499 499
 
@@ -544,7 +544,7 @@  discard block
 block discarded – undo
544 544
 	private function getApplicationClass(string $appName) {
545 545
 		$appNameSpace = App::buildAppNamespace($appName);
546 546
 
547
-		$applicationClassName = $appNameSpace . '\\AppInfo\\Application';
547
+		$applicationClassName = $appNameSpace.'\\AppInfo\\Application';
548 548
 
549 549
 		if (class_exists($applicationClassName)) {
550 550
 			$application = $this->container->get($applicationClassName);
Please login to merge, or discard this patch.
lib/private/Route/CachingRouter.php 2 patches
Indentation   +122 added lines, -122 removed lines patch added patch discarded remove patch
@@ -21,137 +21,137 @@
 block discarded – undo
21 21
 use Symfony\Component\Routing\RouteCollection;
22 22
 
23 23
 class CachingRouter extends Router {
24
-	protected ICache $cache;
24
+    protected ICache $cache;
25 25
 
26
-	protected array $legacyCreatedRoutes = [];
26
+    protected array $legacyCreatedRoutes = [];
27 27
 
28
-	public function __construct(
29
-		ICacheFactory $cacheFactory,
30
-		LoggerInterface $logger,
31
-		IRequest $request,
32
-		IConfig $config,
33
-		IEventLogger $eventLogger,
34
-		ContainerInterface $container,
35
-		IAppManager $appManager,
36
-	) {
37
-		$this->cache = $cacheFactory->createLocal('route');
38
-		parent::__construct($logger, $request, $config, $eventLogger, $container, $appManager);
39
-	}
28
+    public function __construct(
29
+        ICacheFactory $cacheFactory,
30
+        LoggerInterface $logger,
31
+        IRequest $request,
32
+        IConfig $config,
33
+        IEventLogger $eventLogger,
34
+        ContainerInterface $container,
35
+        IAppManager $appManager,
36
+    ) {
37
+        $this->cache = $cacheFactory->createLocal('route');
38
+        parent::__construct($logger, $request, $config, $eventLogger, $container, $appManager);
39
+    }
40 40
 
41
-	/**
42
-	 * Generate url based on $name and $parameters
43
-	 *
44
-	 * @param string $name Name of the route to use.
45
-	 * @param array $parameters Parameters for the route
46
-	 * @param bool $absolute
47
-	 * @return string
48
-	 */
49
-	public function generate($name, $parameters = [], $absolute = false) {
50
-		asort($parameters);
51
-		$key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . $name . sha1(json_encode($parameters)) . (int)$absolute;
52
-		$cachedKey = $this->cache->get($key);
53
-		if ($cachedKey) {
54
-			return $cachedKey;
55
-		} else {
56
-			$url = parent::generate($name, $parameters, $absolute);
57
-			if ($url) {
58
-				$this->cache->set($key, $url, 3600);
59
-			}
60
-			return $url;
61
-		}
62
-	}
41
+    /**
42
+     * Generate url based on $name and $parameters
43
+     *
44
+     * @param string $name Name of the route to use.
45
+     * @param array $parameters Parameters for the route
46
+     * @param bool $absolute
47
+     * @return string
48
+     */
49
+    public function generate($name, $parameters = [], $absolute = false) {
50
+        asort($parameters);
51
+        $key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . $name . sha1(json_encode($parameters)) . (int)$absolute;
52
+        $cachedKey = $this->cache->get($key);
53
+        if ($cachedKey) {
54
+            return $cachedKey;
55
+        } else {
56
+            $url = parent::generate($name, $parameters, $absolute);
57
+            if ($url) {
58
+                $this->cache->set($key, $url, 3600);
59
+            }
60
+            return $url;
61
+        }
62
+    }
63 63
 
64
-	private function serializeRouteCollection(RouteCollection $collection): array {
65
-		$dumper = new CompiledUrlMatcherDumper($collection);
66
-		return $dumper->getCompiledRoutes();
67
-	}
64
+    private function serializeRouteCollection(RouteCollection $collection): array {
65
+        $dumper = new CompiledUrlMatcherDumper($collection);
66
+        return $dumper->getCompiledRoutes();
67
+    }
68 68
 
69
-	/**
70
-	 * Find the route matching $url
71
-	 *
72
-	 * @param string $url The url to find
73
-	 * @throws \Exception
74
-	 * @return array
75
-	 */
76
-	public function findMatchingRoute(string $url): array {
77
-		$this->eventLogger->start('cacheroute:match', 'Match route');
78
-		$key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . '#rootCollection';
79
-		$cachedRoutes = $this->cache->get($key);
80
-		if (!$cachedRoutes) {
81
-			parent::loadRoutes();
82
-			$cachedRoutes = $this->serializeRouteCollection($this->root);
83
-			$this->cache->set($key, $cachedRoutes, 3600);
84
-		}
85
-		$matcher = new CompiledUrlMatcher($cachedRoutes, $this->context);
86
-		$this->eventLogger->start('cacheroute:url:match', 'Symfony URL match call');
87
-		try {
88
-			$parameters = $matcher->match($url);
89
-		} catch (ResourceNotFoundException $e) {
90
-			if (!str_ends_with($url, '/')) {
91
-				// We allow links to apps/files? for backwards compatibility reasons
92
-				// However, since Symfony does not allow empty route names, the route
93
-				// we need to match is '/', so we need to append the '/' here.
94
-				try {
95
-					$parameters = $matcher->match($url . '/');
96
-				} catch (ResourceNotFoundException $newException) {
97
-					// If we still didn't match a route, we throw the original exception
98
-					throw $e;
99
-				}
100
-			} else {
101
-				throw $e;
102
-			}
103
-		}
104
-		$this->eventLogger->end('cacheroute:url:match');
69
+    /**
70
+     * Find the route matching $url
71
+     *
72
+     * @param string $url The url to find
73
+     * @throws \Exception
74
+     * @return array
75
+     */
76
+    public function findMatchingRoute(string $url): array {
77
+        $this->eventLogger->start('cacheroute:match', 'Match route');
78
+        $key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . '#rootCollection';
79
+        $cachedRoutes = $this->cache->get($key);
80
+        if (!$cachedRoutes) {
81
+            parent::loadRoutes();
82
+            $cachedRoutes = $this->serializeRouteCollection($this->root);
83
+            $this->cache->set($key, $cachedRoutes, 3600);
84
+        }
85
+        $matcher = new CompiledUrlMatcher($cachedRoutes, $this->context);
86
+        $this->eventLogger->start('cacheroute:url:match', 'Symfony URL match call');
87
+        try {
88
+            $parameters = $matcher->match($url);
89
+        } catch (ResourceNotFoundException $e) {
90
+            if (!str_ends_with($url, '/')) {
91
+                // We allow links to apps/files? for backwards compatibility reasons
92
+                // However, since Symfony does not allow empty route names, the route
93
+                // we need to match is '/', so we need to append the '/' here.
94
+                try {
95
+                    $parameters = $matcher->match($url . '/');
96
+                } catch (ResourceNotFoundException $newException) {
97
+                    // If we still didn't match a route, we throw the original exception
98
+                    throw $e;
99
+                }
100
+            } else {
101
+                throw $e;
102
+            }
103
+        }
104
+        $this->eventLogger->end('cacheroute:url:match');
105 105
 
106
-		$this->eventLogger->end('cacheroute:match');
107
-		return $parameters;
108
-	}
106
+        $this->eventLogger->end('cacheroute:match');
107
+        return $parameters;
108
+    }
109 109
 
110
-	/**
111
-	 * @param array{action:mixed, ...} $parameters
112
-	 */
113
-	protected function callLegacyActionRoute(array $parameters): void {
114
-		/*
110
+    /**
111
+     * @param array{action:mixed, ...} $parameters
112
+     */
113
+    protected function callLegacyActionRoute(array $parameters): void {
114
+        /*
115 115
 		 * Closures cannot be serialized to cache, so for legacy routes calling an action we have to include the routes.php file again
116 116
 		 */
117
-		$app = $parameters['app'];
118
-		$this->useCollection($app);
119
-		parent::requireRouteFile($parameters['route-file'], $app);
120
-		$collection = $this->getCollection($app);
121
-		$parameters['action'] = $collection->get($parameters['_route'])?->getDefault('action');
122
-		parent::callLegacyActionRoute($parameters);
123
-	}
117
+        $app = $parameters['app'];
118
+        $this->useCollection($app);
119
+        parent::requireRouteFile($parameters['route-file'], $app);
120
+        $collection = $this->getCollection($app);
121
+        $parameters['action'] = $collection->get($parameters['_route'])?->getDefault('action');
122
+        parent::callLegacyActionRoute($parameters);
123
+    }
124 124
 
125
-	/**
126
-	 * Create a \OC\Route\Route.
127
-	 * Deprecated
128
-	 *
129
-	 * @param string $name Name of the route to create.
130
-	 * @param string $pattern The pattern to match
131
-	 * @param array $defaults An array of default parameter values
132
-	 * @param array $requirements An array of requirements for parameters (regexes)
133
-	 */
134
-	public function create($name, $pattern, array $defaults = [], array $requirements = []): Route {
135
-		$this->legacyCreatedRoutes[] = $name;
136
-		return parent::create($name, $pattern, $defaults, $requirements);
137
-	}
125
+    /**
126
+     * Create a \OC\Route\Route.
127
+     * Deprecated
128
+     *
129
+     * @param string $name Name of the route to create.
130
+     * @param string $pattern The pattern to match
131
+     * @param array $defaults An array of default parameter values
132
+     * @param array $requirements An array of requirements for parameters (regexes)
133
+     */
134
+    public function create($name, $pattern, array $defaults = [], array $requirements = []): Route {
135
+        $this->legacyCreatedRoutes[] = $name;
136
+        return parent::create($name, $pattern, $defaults, $requirements);
137
+    }
138 138
 
139
-	/**
140
-	 * Require a routes.php file
141
-	 */
142
-	protected function requireRouteFile(string $file, string $appName): void {
143
-		$this->legacyCreatedRoutes = [];
144
-		parent::requireRouteFile($file, $appName);
145
-		foreach ($this->legacyCreatedRoutes as $routeName) {
146
-			$route = $this->collection?->get($routeName);
147
-			if ($route === null) {
148
-				/* Should never happen */
149
-				throw new \Exception("Could not find route $routeName");
150
-			}
151
-			if ($route->hasDefault('action')) {
152
-				$route->setDefault('route-file', $file);
153
-				$route->setDefault('app', $appName);
154
-			}
155
-		}
156
-	}
139
+    /**
140
+     * Require a routes.php file
141
+     */
142
+    protected function requireRouteFile(string $file, string $appName): void {
143
+        $this->legacyCreatedRoutes = [];
144
+        parent::requireRouteFile($file, $appName);
145
+        foreach ($this->legacyCreatedRoutes as $routeName) {
146
+            $route = $this->collection?->get($routeName);
147
+            if ($route === null) {
148
+                /* Should never happen */
149
+                throw new \Exception("Could not find route $routeName");
150
+            }
151
+            if ($route->hasDefault('action')) {
152
+                $route->setDefault('route-file', $file);
153
+                $route->setDefault('app', $appName);
154
+            }
155
+        }
156
+    }
157 157
 }
Please login to merge, or discard this patch.
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -48,7 +48,7 @@  discard block
 block discarded – undo
48 48
 	 */
49 49
 	public function generate($name, $parameters = [], $absolute = false) {
50 50
 		asort($parameters);
51
-		$key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . $name . sha1(json_encode($parameters)) . (int)$absolute;
51
+		$key = $this->context->getHost().'#'.$this->context->getBaseUrl().$name.sha1(json_encode($parameters)).(int) $absolute;
52 52
 		$cachedKey = $this->cache->get($key);
53 53
 		if ($cachedKey) {
54 54
 			return $cachedKey;
@@ -75,7 +75,7 @@  discard block
 block discarded – undo
75 75
 	 */
76 76
 	public function findMatchingRoute(string $url): array {
77 77
 		$this->eventLogger->start('cacheroute:match', 'Match route');
78
-		$key = $this->context->getHost() . '#' . $this->context->getBaseUrl() . '#rootCollection';
78
+		$key = $this->context->getHost().'#'.$this->context->getBaseUrl().'#rootCollection';
79 79
 		$cachedRoutes = $this->cache->get($key);
80 80
 		if (!$cachedRoutes) {
81 81
 			parent::loadRoutes();
@@ -92,7 +92,7 @@  discard block
 block discarded – undo
92 92
 				// However, since Symfony does not allow empty route names, the route
93 93
 				// we need to match is '/', so we need to append the '/' here.
94 94
 				try {
95
-					$parameters = $matcher->match($url . '/');
95
+					$parameters = $matcher->match($url.'/');
96 96
 				} catch (ResourceNotFoundException $newException) {
97 97
 					// If we still didn't match a route, we throw the original exception
98 98
 					throw $e;
Please login to merge, or discard this patch.