Completed
Push — master ( bdc76b...3da748 )
by
unknown
22:39 queued 14s
created
tests/lib/AppTest.php 1 patch
Indentation   +529 added lines, -529 removed lines patch added patch discarded remove patch
@@ -30,559 +30,559 @@
 block discarded – undo
30 30
  * @group DB
31 31
  */
32 32
 class AppTest extends \Test\TestCase {
33
-	public const TEST_USER1 = 'user1';
34
-	public const TEST_USER2 = 'user2';
35
-	public const TEST_USER3 = 'user3';
36
-	public const TEST_GROUP1 = 'group1';
37
-	public const TEST_GROUP2 = 'group2';
33
+    public const TEST_USER1 = 'user1';
34
+    public const TEST_USER2 = 'user2';
35
+    public const TEST_USER3 = 'user3';
36
+    public const TEST_GROUP1 = 'group1';
37
+    public const TEST_GROUP2 = 'group2';
38 38
 
39
-	public static function appVersionsProvider(): array {
40
-		return [
41
-			// exact match
42
-			[
43
-				'6.0.0.0',
44
-				[
45
-					'requiremin' => '6.0',
46
-					'requiremax' => '6.0',
47
-				],
48
-				true
49
-			],
50
-			// in-between match
51
-			[
52
-				'6.0.0.0',
53
-				[
54
-					'requiremin' => '5.0',
55
-					'requiremax' => '7.0',
56
-				],
57
-				true
58
-			],
59
-			// app too old
60
-			[
61
-				'6.0.0.0',
62
-				[
63
-					'requiremin' => '5.0',
64
-					'requiremax' => '5.0',
65
-				],
66
-				false
67
-			],
68
-			// app too new
69
-			[
70
-				'5.0.0.0',
71
-				[
72
-					'requiremin' => '6.0',
73
-					'requiremax' => '6.0',
74
-				],
75
-				false
76
-			],
77
-			// only min specified
78
-			[
79
-				'6.0.0.0',
80
-				[
81
-					'requiremin' => '6.0',
82
-				],
83
-				true
84
-			],
85
-			// only min specified fail
86
-			[
87
-				'5.0.0.0',
88
-				[
89
-					'requiremin' => '6.0',
90
-				],
91
-				false
92
-			],
93
-			// only min specified legacy
94
-			[
95
-				'6.0.0.0',
96
-				[
97
-					'require' => '6.0',
98
-				],
99
-				true
100
-			],
101
-			// only min specified legacy fail
102
-			[
103
-				'4.0.0.0',
104
-				[
105
-					'require' => '6.0',
106
-				],
107
-				false
108
-			],
109
-			// only max specified
110
-			[
111
-				'5.0.0.0',
112
-				[
113
-					'requiremax' => '6.0',
114
-				],
115
-				true
116
-			],
117
-			// only max specified fail
118
-			[
119
-				'7.0.0.0',
120
-				[
121
-					'requiremax' => '6.0',
122
-				],
123
-				false
124
-			],
125
-			// variations of versions
126
-			// single OC number
127
-			[
128
-				'4',
129
-				[
130
-					'require' => '4.0',
131
-				],
132
-				true
133
-			],
134
-			// multiple OC number
135
-			[
136
-				'4.3.1',
137
-				[
138
-					'require' => '4.3',
139
-				],
140
-				true
141
-			],
142
-			// single app number
143
-			[
144
-				'4',
145
-				[
146
-					'require' => '4',
147
-				],
148
-				true
149
-			],
150
-			// single app number fail
151
-			[
152
-				'4.3',
153
-				[
154
-					'require' => '5',
155
-				],
156
-				false
157
-			],
158
-			// complex
159
-			[
160
-				'5.0.0',
161
-				[
162
-					'require' => '4.5.1',
163
-				],
164
-				true
165
-			],
166
-			// complex fail
167
-			[
168
-				'4.3.1',
169
-				[
170
-					'require' => '4.3.2',
171
-				],
172
-				false
173
-			],
174
-			// two numbers
175
-			[
176
-				'4.3.1',
177
-				[
178
-					'require' => '4.4',
179
-				],
180
-				false
181
-			],
182
-			// one number fail
183
-			[
184
-				'4.3.1',
185
-				[
186
-					'require' => '5',
187
-				],
188
-				false
189
-			],
190
-			// pre-alpha app
191
-			[
192
-				'5.0.3',
193
-				[
194
-					'require' => '4.93',
195
-				],
196
-				true
197
-			],
198
-			// pre-alpha OC
199
-			[
200
-				'6.90.0.2',
201
-				[
202
-					'require' => '6.90',
203
-				],
204
-				true
205
-			],
206
-			// pre-alpha OC max
207
-			[
208
-				'6.90.0.2',
209
-				[
210
-					'requiremax' => '7',
211
-				],
212
-				true
213
-			],
214
-			// expect same major number match
215
-			[
216
-				'5.0.3',
217
-				[
218
-					'require' => '5',
219
-				],
220
-				true
221
-			],
222
-			// expect same major number match
223
-			[
224
-				'5.0.3',
225
-				[
226
-					'requiremax' => '5',
227
-				],
228
-				true
229
-			],
230
-			// dependencies versions before require*
231
-			[
232
-				'6.0.0.0',
233
-				[
234
-					'requiremin' => '5.0',
235
-					'requiremax' => '7.0',
236
-					'dependencies' => [
237
-						'owncloud' => [
238
-							'@attributes' => [
239
-								'min-version' => '7.0',
240
-								'max-version' => '7.0',
241
-							],
242
-						],
243
-					],
244
-				],
245
-				false
246
-			],
247
-			[
248
-				'6.0.0.0',
249
-				[
250
-					'requiremin' => '5.0',
251
-					'requiremax' => '7.0',
252
-					'dependencies' => [
253
-						'owncloud' => [
254
-							'@attributes' => [
255
-								'min-version' => '5.0',
256
-								'max-version' => '5.0',
257
-							],
258
-						],
259
-					],
260
-				],
261
-				false
262
-			],
263
-			[
264
-				'6.0.0.0',
265
-				[
266
-					'requiremin' => '5.0',
267
-					'requiremax' => '5.0',
268
-					'dependencies' => [
269
-						'owncloud' => [
270
-							'@attributes' => [
271
-								'min-version' => '5.0',
272
-								'max-version' => '7.0',
273
-							],
274
-						],
275
-					],
276
-				],
277
-				true
278
-			],
279
-			[
280
-				'9.2.0.0',
281
-				[
282
-					'dependencies' => [
283
-						'owncloud' => [
284
-							'@attributes' => [
285
-								'min-version' => '9.0',
286
-								'max-version' => '9.1',
287
-							],
288
-						],
289
-						'nextcloud' => [
290
-							'@attributes' => [
291
-								'min-version' => '9.1',
292
-								'max-version' => '9.2',
293
-							],
294
-						],
295
-					],
296
-				],
297
-				true
298
-			],
299
-			[
300
-				'9.2.0.0',
301
-				[
302
-					'dependencies' => [
303
-						'nextcloud' => [
304
-							'@attributes' => [
305
-								'min-version' => '9.1',
306
-								'max-version' => '9.2',
307
-							],
308
-						],
309
-					],
310
-				],
311
-				true
312
-			],
313
-		];
314
-	}
39
+    public static function appVersionsProvider(): array {
40
+        return [
41
+            // exact match
42
+            [
43
+                '6.0.0.0',
44
+                [
45
+                    'requiremin' => '6.0',
46
+                    'requiremax' => '6.0',
47
+                ],
48
+                true
49
+            ],
50
+            // in-between match
51
+            [
52
+                '6.0.0.0',
53
+                [
54
+                    'requiremin' => '5.0',
55
+                    'requiremax' => '7.0',
56
+                ],
57
+                true
58
+            ],
59
+            // app too old
60
+            [
61
+                '6.0.0.0',
62
+                [
63
+                    'requiremin' => '5.0',
64
+                    'requiremax' => '5.0',
65
+                ],
66
+                false
67
+            ],
68
+            // app too new
69
+            [
70
+                '5.0.0.0',
71
+                [
72
+                    'requiremin' => '6.0',
73
+                    'requiremax' => '6.0',
74
+                ],
75
+                false
76
+            ],
77
+            // only min specified
78
+            [
79
+                '6.0.0.0',
80
+                [
81
+                    'requiremin' => '6.0',
82
+                ],
83
+                true
84
+            ],
85
+            // only min specified fail
86
+            [
87
+                '5.0.0.0',
88
+                [
89
+                    'requiremin' => '6.0',
90
+                ],
91
+                false
92
+            ],
93
+            // only min specified legacy
94
+            [
95
+                '6.0.0.0',
96
+                [
97
+                    'require' => '6.0',
98
+                ],
99
+                true
100
+            ],
101
+            // only min specified legacy fail
102
+            [
103
+                '4.0.0.0',
104
+                [
105
+                    'require' => '6.0',
106
+                ],
107
+                false
108
+            ],
109
+            // only max specified
110
+            [
111
+                '5.0.0.0',
112
+                [
113
+                    'requiremax' => '6.0',
114
+                ],
115
+                true
116
+            ],
117
+            // only max specified fail
118
+            [
119
+                '7.0.0.0',
120
+                [
121
+                    'requiremax' => '6.0',
122
+                ],
123
+                false
124
+            ],
125
+            // variations of versions
126
+            // single OC number
127
+            [
128
+                '4',
129
+                [
130
+                    'require' => '4.0',
131
+                ],
132
+                true
133
+            ],
134
+            // multiple OC number
135
+            [
136
+                '4.3.1',
137
+                [
138
+                    'require' => '4.3',
139
+                ],
140
+                true
141
+            ],
142
+            // single app number
143
+            [
144
+                '4',
145
+                [
146
+                    'require' => '4',
147
+                ],
148
+                true
149
+            ],
150
+            // single app number fail
151
+            [
152
+                '4.3',
153
+                [
154
+                    'require' => '5',
155
+                ],
156
+                false
157
+            ],
158
+            // complex
159
+            [
160
+                '5.0.0',
161
+                [
162
+                    'require' => '4.5.1',
163
+                ],
164
+                true
165
+            ],
166
+            // complex fail
167
+            [
168
+                '4.3.1',
169
+                [
170
+                    'require' => '4.3.2',
171
+                ],
172
+                false
173
+            ],
174
+            // two numbers
175
+            [
176
+                '4.3.1',
177
+                [
178
+                    'require' => '4.4',
179
+                ],
180
+                false
181
+            ],
182
+            // one number fail
183
+            [
184
+                '4.3.1',
185
+                [
186
+                    'require' => '5',
187
+                ],
188
+                false
189
+            ],
190
+            // pre-alpha app
191
+            [
192
+                '5.0.3',
193
+                [
194
+                    'require' => '4.93',
195
+                ],
196
+                true
197
+            ],
198
+            // pre-alpha OC
199
+            [
200
+                '6.90.0.2',
201
+                [
202
+                    'require' => '6.90',
203
+                ],
204
+                true
205
+            ],
206
+            // pre-alpha OC max
207
+            [
208
+                '6.90.0.2',
209
+                [
210
+                    'requiremax' => '7',
211
+                ],
212
+                true
213
+            ],
214
+            // expect same major number match
215
+            [
216
+                '5.0.3',
217
+                [
218
+                    'require' => '5',
219
+                ],
220
+                true
221
+            ],
222
+            // expect same major number match
223
+            [
224
+                '5.0.3',
225
+                [
226
+                    'requiremax' => '5',
227
+                ],
228
+                true
229
+            ],
230
+            // dependencies versions before require*
231
+            [
232
+                '6.0.0.0',
233
+                [
234
+                    'requiremin' => '5.0',
235
+                    'requiremax' => '7.0',
236
+                    'dependencies' => [
237
+                        'owncloud' => [
238
+                            '@attributes' => [
239
+                                'min-version' => '7.0',
240
+                                'max-version' => '7.0',
241
+                            ],
242
+                        ],
243
+                    ],
244
+                ],
245
+                false
246
+            ],
247
+            [
248
+                '6.0.0.0',
249
+                [
250
+                    'requiremin' => '5.0',
251
+                    'requiremax' => '7.0',
252
+                    'dependencies' => [
253
+                        'owncloud' => [
254
+                            '@attributes' => [
255
+                                'min-version' => '5.0',
256
+                                'max-version' => '5.0',
257
+                            ],
258
+                        ],
259
+                    ],
260
+                ],
261
+                false
262
+            ],
263
+            [
264
+                '6.0.0.0',
265
+                [
266
+                    'requiremin' => '5.0',
267
+                    'requiremax' => '5.0',
268
+                    'dependencies' => [
269
+                        'owncloud' => [
270
+                            '@attributes' => [
271
+                                'min-version' => '5.0',
272
+                                'max-version' => '7.0',
273
+                            ],
274
+                        ],
275
+                    ],
276
+                ],
277
+                true
278
+            ],
279
+            [
280
+                '9.2.0.0',
281
+                [
282
+                    'dependencies' => [
283
+                        'owncloud' => [
284
+                            '@attributes' => [
285
+                                'min-version' => '9.0',
286
+                                'max-version' => '9.1',
287
+                            ],
288
+                        ],
289
+                        'nextcloud' => [
290
+                            '@attributes' => [
291
+                                'min-version' => '9.1',
292
+                                'max-version' => '9.2',
293
+                            ],
294
+                        ],
295
+                    ],
296
+                ],
297
+                true
298
+            ],
299
+            [
300
+                '9.2.0.0',
301
+                [
302
+                    'dependencies' => [
303
+                        'nextcloud' => [
304
+                            '@attributes' => [
305
+                                'min-version' => '9.1',
306
+                                'max-version' => '9.2',
307
+                            ],
308
+                        ],
309
+                    ],
310
+                ],
311
+                true
312
+            ],
313
+        ];
314
+    }
315 315
 
316
-	#[\PHPUnit\Framework\Attributes\DataProvider('appVersionsProvider')]
317
-	public function testIsAppCompatible($ocVersion, $appInfo, $expectedResult): void {
318
-		$this->assertEquals($expectedResult, \OC_App::isAppCompatible($ocVersion, $appInfo));
319
-	}
316
+    #[\PHPUnit\Framework\Attributes\DataProvider('appVersionsProvider')]
317
+    public function testIsAppCompatible($ocVersion, $appInfo, $expectedResult): void {
318
+        $this->assertEquals($expectedResult, \OC_App::isAppCompatible($ocVersion, $appInfo));
319
+    }
320 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
-	}
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 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
-	}
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 465
 
466
-	/**
467
-	 * Test enabled apps
468
-	 */
469
-	#[\PHPUnit\Framework\Attributes\DataProvider('appConfigValuesProvider')]
470
-	public function testEnabledApps($user, $expectedApps, $forceAll): void {
471
-		$userManager = Server::get(IUserManager::class);
472
-		$groupManager = Server::get(IGroupManager::class);
473
-		$user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
474
-		$user2 = $userManager->createUser(self::TEST_USER2, 'NotAnEasyPassword123456_');
475
-		$user3 = $userManager->createUser(self::TEST_USER3, 'NotAnEasyPassword123456?');
466
+    /**
467
+     * Test enabled apps
468
+     */
469
+    #[\PHPUnit\Framework\Attributes\DataProvider('appConfigValuesProvider')]
470
+    public function testEnabledApps($user, $expectedApps, $forceAll): void {
471
+        $userManager = Server::get(IUserManager::class);
472
+        $groupManager = Server::get(IGroupManager::class);
473
+        $user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
474
+        $user2 = $userManager->createUser(self::TEST_USER2, 'NotAnEasyPassword123456_');
475
+        $user3 = $userManager->createUser(self::TEST_USER3, 'NotAnEasyPassword123456?');
476 476
 
477
-		$group1 = $groupManager->createGroup(self::TEST_GROUP1);
478
-		$group1->addUser($user1);
479
-		$group1->addUser($user3);
480
-		$group2 = $groupManager->createGroup(self::TEST_GROUP2);
481
-		$group2->addUser($user2);
482
-		$group2->addUser($user3);
477
+        $group1 = $groupManager->createGroup(self::TEST_GROUP1);
478
+        $group1->addUser($user1);
479
+        $group1->addUser($user3);
480
+        $group2 = $groupManager->createGroup(self::TEST_GROUP2);
481
+        $group2->addUser($user2);
482
+        $group2->addUser($user3);
483 483
 
484
-		\OC_User::setUserId($user);
484
+        \OC_User::setUserId($user);
485 485
 
486
-		$this->setupAppConfigMock()->expects($this->once())
487
-			->method('searchValues')
488
-			->willReturn(
489
-				[
490
-					'app3' => 'yes',
491
-					'app2' => 'no',
492
-					'app1' => 'yes',
493
-					'appforgroup1' => '["group1"]',
494
-					'appforgroup2' => '["group2"]',
495
-					'appforgroup12' => '["group2","group1"]',
496
-				]
497
-			);
486
+        $this->setupAppConfigMock()->expects($this->once())
487
+            ->method('searchValues')
488
+            ->willReturn(
489
+                [
490
+                    'app3' => 'yes',
491
+                    'app2' => 'no',
492
+                    'app1' => 'yes',
493
+                    'appforgroup1' => '["group1"]',
494
+                    'appforgroup2' => '["group2"]',
495
+                    'appforgroup12' => '["group2","group1"]',
496
+                ]
497
+            );
498 498
 
499
-		$apps = \OC_App::getEnabledApps(false, $forceAll);
499
+        $apps = \OC_App::getEnabledApps(false, $forceAll);
500 500
 
501
-		$this->restoreAppConfig();
502
-		\OC_User::setUserId(null);
501
+        $this->restoreAppConfig();
502
+        \OC_User::setUserId(null);
503 503
 
504
-		$user1->delete();
505
-		$user2->delete();
506
-		$user3->delete();
504
+        $user1->delete();
505
+        $user2->delete();
506
+        $user3->delete();
507 507
 
508
-		$group1->delete();
509
-		$group2->delete();
508
+        $group1->delete();
509
+        $group2->delete();
510 510
 
511
-		$this->assertEquals($expectedApps, $apps);
512
-	}
511
+        $this->assertEquals($expectedApps, $apps);
512
+    }
513 513
 
514
-	/**
515
-	 * Test isEnabledApps() with cache, not re-reading the list of
516
-	 * enabled apps more than once when a user is set.
517
-	 */
518
-	public function testEnabledAppsCache(): void {
519
-		$userManager = Server::get(IUserManager::class);
520
-		$user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
514
+    /**
515
+     * Test isEnabledApps() with cache, not re-reading the list of
516
+     * enabled apps more than once when a user is set.
517
+     */
518
+    public function testEnabledAppsCache(): void {
519
+        $userManager = Server::get(IUserManager::class);
520
+        $user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
521 521
 
522
-		\OC_User::setUserId(self::TEST_USER1);
522
+        \OC_User::setUserId(self::TEST_USER1);
523 523
 
524
-		$this->setupAppConfigMock()->expects($this->once())
525
-			->method('searchValues')
526
-			->willReturn(
527
-				[
528
-					'app3' => 'yes',
529
-					'app2' => 'no',
530
-				]
531
-			);
524
+        $this->setupAppConfigMock()->expects($this->once())
525
+            ->method('searchValues')
526
+            ->willReturn(
527
+                [
528
+                    'app3' => 'yes',
529
+                    'app2' => 'no',
530
+                ]
531
+            );
532 532
 
533
-		$apps = \OC_App::getEnabledApps();
534
-		$this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
533
+        $apps = \OC_App::getEnabledApps();
534
+        $this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
535 535
 
536
-		// mock should not be called again here
537
-		$apps = \OC_App::getEnabledApps();
538
-		$this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
536
+        // mock should not be called again here
537
+        $apps = \OC_App::getEnabledApps();
538
+        $this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
539 539
 
540
-		$this->restoreAppConfig();
541
-		\OC_User::setUserId(null);
540
+        $this->restoreAppConfig();
541
+        \OC_User::setUserId(null);
542 542
 
543
-		$user1->delete();
544
-	}
543
+        $user1->delete();
544
+    }
545 545
 
546 546
 
547
-	private function setupAppConfigMock() {
548
-		/** @var AppConfig|MockObject */
549
-		$appConfig = $this->getMockBuilder(AppConfig::class)
550
-			->onlyMethods(['searchValues'])
551
-			->setConstructorArgs([Server::get(IDBConnection::class)])
552
-			->disableOriginalConstructor()
553
-			->getMock();
547
+    private function setupAppConfigMock() {
548
+        /** @var AppConfig|MockObject */
549
+        $appConfig = $this->getMockBuilder(AppConfig::class)
550
+            ->onlyMethods(['searchValues'])
551
+            ->setConstructorArgs([Server::get(IDBConnection::class)])
552
+            ->disableOriginalConstructor()
553
+            ->getMock();
554 554
 
555
-		$this->registerAppConfig($appConfig);
556
-		return $appConfig;
557
-	}
555
+        $this->registerAppConfig($appConfig);
556
+        return $appConfig;
557
+    }
558 558
 
559
-	/**
560
-	 * Register an app config mock for testing purposes.
561
-	 *
562
-	 * @param IAppConfig $appConfig app config mock
563
-	 */
564
-	private function registerAppConfig(AppConfig $appConfig) {
565
-		$this->overwriteService(AppConfig::class, $appConfig);
566
-		$this->overwriteService(AppManager::class, new AppManager(
567
-			Server::get(IUserSession::class),
568
-			Server::get(IConfig::class),
569
-			Server::get(IGroupManager::class),
570
-			Server::get(ICacheFactory::class),
571
-			Server::get(IEventDispatcher::class),
572
-			Server::get(LoggerInterface::class),
573
-			Server::get(ServerVersion::class),
574
-			Server::get(ConfigManager::class),
575
-		));
576
-	}
559
+    /**
560
+     * Register an app config mock for testing purposes.
561
+     *
562
+     * @param IAppConfig $appConfig app config mock
563
+     */
564
+    private function registerAppConfig(AppConfig $appConfig) {
565
+        $this->overwriteService(AppConfig::class, $appConfig);
566
+        $this->overwriteService(AppManager::class, new AppManager(
567
+            Server::get(IUserSession::class),
568
+            Server::get(IConfig::class),
569
+            Server::get(IGroupManager::class),
570
+            Server::get(ICacheFactory::class),
571
+            Server::get(IEventDispatcher::class),
572
+            Server::get(LoggerInterface::class),
573
+            Server::get(ServerVersion::class),
574
+            Server::get(ConfigManager::class),
575
+        ));
576
+    }
577 577
 
578
-	/**
579
-	 * Restore the original app config service.
580
-	 */
581
-	private function restoreAppConfig() {
582
-		$this->restoreService(AppConfig::class);
583
-		$this->restoreService(AppManager::class);
578
+    /**
579
+     * Restore the original app config service.
580
+     */
581
+    private function restoreAppConfig() {
582
+        $this->restoreService(AppConfig::class);
583
+        $this->restoreService(AppManager::class);
584 584
 
585
-		// Remove the cache of the mocked apps list with a forceRefresh
586
-		\OC_App::getEnabledApps();
587
-	}
585
+        // Remove the cache of the mocked apps list with a forceRefresh
586
+        \OC_App::getEnabledApps();
587
+    }
588 588
 }
Please login to merge, or discard this patch.
tests/lib/App/InfoParserTest.php 2 patches
Indentation   +82 added lines, -82 removed lines patch added patch discarded remove patch
@@ -13,97 +13,97 @@
 block discarded – undo
13 13
 use Test\TestCase;
14 14
 
15 15
 class InfoParserTest extends TestCase {
16
-	/** @var CappedMemoryCache */
17
-	private static $cache;
16
+    /** @var CappedMemoryCache */
17
+    private static $cache;
18 18
 
19
-	public static function setUpBeforeClass(): void {
20
-		self::$cache = new CappedMemoryCache();
21
-	}
19
+    public static function setUpBeforeClass(): void {
20
+        self::$cache = new CappedMemoryCache();
21
+    }
22 22
 
23
-	public function parserTest($expectedJson, $xmlFile, $cache = null) {
24
-		$parser = new InfoParser($cache);
23
+    public function parserTest($expectedJson, $xmlFile, $cache = null) {
24
+        $parser = new InfoParser($cache);
25 25
 
26
-		$expectedData = null;
27
-		if (!is_null($expectedJson)) {
28
-			$expectedData = json_decode(file_get_contents(OC::$SERVERROOT . "/tests/data/app/$expectedJson"), true);
29
-		}
30
-		$data = $parser->parse(OC::$SERVERROOT . "/tests/data/app/$xmlFile");
26
+        $expectedData = null;
27
+        if (!is_null($expectedJson)) {
28
+            $expectedData = json_decode(file_get_contents(OC::$SERVERROOT . "/tests/data/app/$expectedJson"), true);
29
+        }
30
+        $data = $parser->parse(OC::$SERVERROOT . "/tests/data/app/$xmlFile");
31 31
 
32
-		$this->assertEquals($expectedData, $data);
33
-	}
32
+        $this->assertEquals($expectedData, $data);
33
+    }
34 34
 
35
-	#[\PHPUnit\Framework\Attributes\DataProvider('providesInfoXml')]
36
-	public function testParsingValidXmlWithoutCache($expectedJson, $xmlFile): void {
37
-		$this->parserTest($expectedJson, $xmlFile);
38
-	}
35
+    #[\PHPUnit\Framework\Attributes\DataProvider('providesInfoXml')]
36
+    public function testParsingValidXmlWithoutCache($expectedJson, $xmlFile): void {
37
+        $this->parserTest($expectedJson, $xmlFile);
38
+    }
39 39
 
40
-	#[\PHPUnit\Framework\Attributes\DataProvider('providesInfoXml')]
41
-	public function testParsingValidXmlWithCache($expectedJson, $xmlFile): void {
42
-		$this->parserTest($expectedJson, $xmlFile, self::$cache);
43
-	}
40
+    #[\PHPUnit\Framework\Attributes\DataProvider('providesInfoXml')]
41
+    public function testParsingValidXmlWithCache($expectedJson, $xmlFile): void {
42
+        $this->parserTest($expectedJson, $xmlFile, self::$cache);
43
+    }
44 44
 
45
-	public static function providesInfoXml(): array {
46
-		return [
47
-			['expected-info.json', 'valid-info.xml'],
48
-			[null, 'invalid-info.xml'],
49
-			['expected-info.json', 'valid-info.xml'],
50
-			[null, 'invalid-info.xml'],
51
-			['navigation-one-item.json', 'navigation-one-item.xml'],
52
-			['navigation-two-items.json', 'navigation-two-items.xml'],
53
-			['various-single-item.json', 'various-single-item.xml'],
54
-		];
55
-	}
45
+    public static function providesInfoXml(): array {
46
+        return [
47
+            ['expected-info.json', 'valid-info.xml'],
48
+            [null, 'invalid-info.xml'],
49
+            ['expected-info.json', 'valid-info.xml'],
50
+            [null, 'invalid-info.xml'],
51
+            ['navigation-one-item.json', 'navigation-one-item.xml'],
52
+            ['navigation-two-items.json', 'navigation-two-items.xml'],
53
+            ['various-single-item.json', 'various-single-item.xml'],
54
+        ];
55
+    }
56 56
 
57
-	/**
58
-	 * Providers for the app data values
59
-	 */
60
-	public static function appDataProvider(): array {
61
-		return [
62
-			[
63
-				['description' => " \t  This is a multiline \n test with \n \t \n \n some new lines   "],
64
-				['description' => "This is a multiline \n test with \n \t \n \n some new lines"],
65
-			],
66
-			[
67
-				['description' => " \t  This is a multiline \n test with \n \t   some new lines   "],
68
-				['description' => "This is a multiline \n test with \n \t   some new lines"],
69
-			],
70
-			[
71
-				['description' => hex2bin('5065726d657420646520732761757468656e7469666965722064616e732070697769676f20646972656374656d656e74206176656320736573206964656e74696669616e7473206f776e636c6f75642073616e73206c65732072657461706572206574206d657420c3a0206a6f757273206365757820636920656e20636173206465206368616e67656d656e74206465206d6f742064652070617373652e0d0a0d')],
72
-				['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."],
73
-			],
74
-			[
75
-				['not-a-description' => " \t  This is a multiline \n test with \n \t   some new lines   "],
76
-				[
77
-					'not-a-description' => " \t  This is a multiline \n test with \n \t   some new lines   ",
78
-					'description' => '',
79
-				],
80
-			],
81
-			[
82
-				['description' => [100, 'bla']],
83
-				['description' => ''],
84
-			],
85
-		];
86
-	}
57
+    /**
58
+     * Providers for the app data values
59
+     */
60
+    public static function appDataProvider(): array {
61
+        return [
62
+            [
63
+                ['description' => " \t  This is a multiline \n test with \n \t \n \n some new lines   "],
64
+                ['description' => "This is a multiline \n test with \n \t \n \n some new lines"],
65
+            ],
66
+            [
67
+                ['description' => " \t  This is a multiline \n test with \n \t   some new lines   "],
68
+                ['description' => "This is a multiline \n test with \n \t   some new lines"],
69
+            ],
70
+            [
71
+                ['description' => hex2bin('5065726d657420646520732761757468656e7469666965722064616e732070697769676f20646972656374656d656e74206176656320736573206964656e74696669616e7473206f776e636c6f75642073616e73206c65732072657461706572206574206d657420c3a0206a6f757273206365757820636920656e20636173206465206368616e67656d656e74206465206d6f742064652070617373652e0d0a0d')],
72
+                ['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."],
73
+            ],
74
+            [
75
+                ['not-a-description' => " \t  This is a multiline \n test with \n \t   some new lines   "],
76
+                [
77
+                    'not-a-description' => " \t  This is a multiline \n test with \n \t   some new lines   ",
78
+                    'description' => '',
79
+                ],
80
+            ],
81
+            [
82
+                ['description' => [100, 'bla']],
83
+                ['description' => ''],
84
+            ],
85
+        ];
86
+    }
87 87
 
88
-	/**
89
-	 * Test app info parser
90
-	 */
91
-	#[\PHPUnit\Framework\Attributes\DataProvider('appDataProvider')]
92
-	public function testApplyL10NNoLanguage(array $data, array $expected): void {
93
-		$parser = new InfoParser();
94
-		$this->assertSame($expected, $parser->applyL10N($data));
95
-	}
88
+    /**
89
+     * Test app info parser
90
+     */
91
+    #[\PHPUnit\Framework\Attributes\DataProvider('appDataProvider')]
92
+    public function testApplyL10NNoLanguage(array $data, array $expected): void {
93
+        $parser = new InfoParser();
94
+        $this->assertSame($expected, $parser->applyL10N($data));
95
+    }
96 96
 
97
-	public function testApplyL10N(): void {
98
-		$parser = new InfoParser();
99
-		$data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-multi-lang.xml');
100
-		$this->assertEquals('English', $parser->applyL10N($data, 'en')['description']);
101
-		$this->assertEquals('German', $parser->applyL10N($data, 'de')['description']);
102
-	}
97
+    public function testApplyL10N(): void {
98
+        $parser = new InfoParser();
99
+        $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-multi-lang.xml');
100
+        $this->assertEquals('English', $parser->applyL10N($data, 'en')['description']);
101
+        $this->assertEquals('German', $parser->applyL10N($data, 'de')['description']);
102
+    }
103 103
 
104
-	public function testApplyL10NSingleLanguage(): void {
105
-		$parser = new InfoParser();
106
-		$data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-single-lang.xml');
107
-		$this->assertEquals('English', $parser->applyL10N($data, 'en')['description']);
108
-	}
104
+    public function testApplyL10NSingleLanguage(): void {
105
+        $parser = new InfoParser();
106
+        $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-single-lang.xml');
107
+        $this->assertEquals('English', $parser->applyL10N($data, 'en')['description']);
108
+    }
109 109
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -25,9 +25,9 @@  discard block
 block discarded – undo
25 25
 
26 26
 		$expectedData = null;
27 27
 		if (!is_null($expectedJson)) {
28
-			$expectedData = json_decode(file_get_contents(OC::$SERVERROOT . "/tests/data/app/$expectedJson"), true);
28
+			$expectedData = json_decode(file_get_contents(OC::$SERVERROOT."/tests/data/app/$expectedJson"), true);
29 29
 		}
30
-		$data = $parser->parse(OC::$SERVERROOT . "/tests/data/app/$xmlFile");
30
+		$data = $parser->parse(OC::$SERVERROOT."/tests/data/app/$xmlFile");
31 31
 
32 32
 		$this->assertEquals($expectedData, $data);
33 33
 	}
@@ -96,14 +96,14 @@  discard block
 block discarded – undo
96 96
 
97 97
 	public function testApplyL10N(): void {
98 98
 		$parser = new InfoParser();
99
-		$data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-multi-lang.xml');
99
+		$data = $parser->parse(\OC::$SERVERROOT.'/tests/data/app/description-multi-lang.xml');
100 100
 		$this->assertEquals('English', $parser->applyL10N($data, 'en')['description']);
101 101
 		$this->assertEquals('German', $parser->applyL10N($data, 'de')['description']);
102 102
 	}
103 103
 
104 104
 	public function testApplyL10NSingleLanguage(): void {
105 105
 		$parser = new InfoParser();
106
-		$data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-single-lang.xml');
106
+		$data = $parser->parse(\OC::$SERVERROOT.'/tests/data/app/description-single-lang.xml');
107 107
 		$this->assertEquals('English', $parser->applyL10N($data, 'en')['description']);
108 108
 	}
109 109
 }
Please login to merge, or discard this patch.
tests/lib/App/DependencyAnalyzerTest.php 1 patch
Indentation   +446 added lines, -446 removed lines patch added patch discarded remove patch
@@ -13,476 +13,476 @@
 block discarded – undo
13 13
 use Test\TestCase;
14 14
 
15 15
 class DependencyAnalyzerTest extends TestCase {
16
-	/** @var Platform|\PHPUnit\Framework\MockObject\MockObject */
17
-	private $platformMock;
16
+    /** @var Platform|\PHPUnit\Framework\MockObject\MockObject */
17
+    private $platformMock;
18 18
 
19
-	/** @var IL10N */
20
-	private $l10nMock;
19
+    /** @var IL10N */
20
+    private $l10nMock;
21 21
 
22
-	/** @var DependencyAnalyzer */
23
-	private $analyser;
22
+    /** @var DependencyAnalyzer */
23
+    private $analyser;
24 24
 
25
-	protected function setUp(): void {
26
-		$this->platformMock = $this->getMockBuilder(Platform::class)
27
-			->disableOriginalConstructor()
28
-			->getMock();
29
-		$this->platformMock->expects($this->any())
30
-			->method('getPhpVersion')
31
-			->willReturn('5.4.3');
32
-		$this->platformMock->expects($this->any())
33
-			->method('getIntSize')
34
-			->willReturn(4);
35
-		$this->platformMock->expects($this->any())
36
-			->method('getDatabase')
37
-			->willReturn('mysql');
38
-		$this->platformMock->expects($this->any())
39
-			->method('getOS')
40
-			->willReturn('Linux');
41
-		$this->platformMock->expects($this->any())
42
-			->method('isCommandKnown')
43
-			->willReturnCallback(function ($command) {
44
-				return ($command === 'grep');
45
-			});
46
-		$this->platformMock->expects($this->any())
47
-			->method('getLibraryVersion')
48
-			->willReturnCallback(function ($lib) {
49
-				if ($lib === 'curl') {
50
-					return '2.3.4';
51
-				}
52
-				return null;
53
-			});
54
-		$this->platformMock->expects($this->any())
55
-			->method('getOcVersion')
56
-			->willReturn('8.0.2');
25
+    protected function setUp(): void {
26
+        $this->platformMock = $this->getMockBuilder(Platform::class)
27
+            ->disableOriginalConstructor()
28
+            ->getMock();
29
+        $this->platformMock->expects($this->any())
30
+            ->method('getPhpVersion')
31
+            ->willReturn('5.4.3');
32
+        $this->platformMock->expects($this->any())
33
+            ->method('getIntSize')
34
+            ->willReturn(4);
35
+        $this->platformMock->expects($this->any())
36
+            ->method('getDatabase')
37
+            ->willReturn('mysql');
38
+        $this->platformMock->expects($this->any())
39
+            ->method('getOS')
40
+            ->willReturn('Linux');
41
+        $this->platformMock->expects($this->any())
42
+            ->method('isCommandKnown')
43
+            ->willReturnCallback(function ($command) {
44
+                return ($command === 'grep');
45
+            });
46
+        $this->platformMock->expects($this->any())
47
+            ->method('getLibraryVersion')
48
+            ->willReturnCallback(function ($lib) {
49
+                if ($lib === 'curl') {
50
+                    return '2.3.4';
51
+                }
52
+                return null;
53
+            });
54
+        $this->platformMock->expects($this->any())
55
+            ->method('getOcVersion')
56
+            ->willReturn('8.0.2');
57 57
 
58
-		$this->l10nMock = $this->getMockBuilder(IL10N::class)
59
-			->disableOriginalConstructor()
60
-			->getMock();
61
-		$this->l10nMock->expects($this->any())
62
-			->method('t')
63
-			->willReturnCallback(function ($text, $parameters = []) {
64
-				return vsprintf($text, $parameters);
65
-			});
58
+        $this->l10nMock = $this->getMockBuilder(IL10N::class)
59
+            ->disableOriginalConstructor()
60
+            ->getMock();
61
+        $this->l10nMock->expects($this->any())
62
+            ->method('t')
63
+            ->willReturnCallback(function ($text, $parameters = []) {
64
+                return vsprintf($text, $parameters);
65
+            });
66 66
 
67
-		$this->analyser = new DependencyAnalyzer($this->platformMock, $this->l10nMock);
68
-	}
67
+        $this->analyser = new DependencyAnalyzer($this->platformMock, $this->l10nMock);
68
+    }
69 69
 
70
-	/**
71
-	 *
72
-	 * @param string $expectedMissing
73
-	 * @param string $minVersion
74
-	 * @param string $maxVersion
75
-	 * @param string $intSize
76
-	 */
77
-	#[\PHPUnit\Framework\Attributes\DataProvider('providesPhpVersion')]
78
-	public function testPhpVersion($expectedMissing, $minVersion, $maxVersion, $intSize): void {
79
-		$app = [
80
-			'dependencies' => [
81
-				'php' => []
82
-			]
83
-		];
84
-		if (!is_null($minVersion)) {
85
-			$app['dependencies']['php']['@attributes']['min-version'] = $minVersion;
86
-		}
87
-		if (!is_null($maxVersion)) {
88
-			$app['dependencies']['php']['@attributes']['max-version'] = $maxVersion;
89
-		}
90
-		if (!is_null($intSize)) {
91
-			$app['dependencies']['php']['@attributes']['min-int-size'] = $intSize;
92
-		}
93
-		$missing = $this->analyser->analyze($app);
70
+    /**
71
+     *
72
+     * @param string $expectedMissing
73
+     * @param string $minVersion
74
+     * @param string $maxVersion
75
+     * @param string $intSize
76
+     */
77
+    #[\PHPUnit\Framework\Attributes\DataProvider('providesPhpVersion')]
78
+    public function testPhpVersion($expectedMissing, $minVersion, $maxVersion, $intSize): void {
79
+        $app = [
80
+            'dependencies' => [
81
+                'php' => []
82
+            ]
83
+        ];
84
+        if (!is_null($minVersion)) {
85
+            $app['dependencies']['php']['@attributes']['min-version'] = $minVersion;
86
+        }
87
+        if (!is_null($maxVersion)) {
88
+            $app['dependencies']['php']['@attributes']['max-version'] = $maxVersion;
89
+        }
90
+        if (!is_null($intSize)) {
91
+            $app['dependencies']['php']['@attributes']['min-int-size'] = $intSize;
92
+        }
93
+        $missing = $this->analyser->analyze($app);
94 94
 
95
-		$this->assertTrue(is_array($missing));
96
-		$this->assertEquals($expectedMissing, $missing);
97
-	}
95
+        $this->assertTrue(is_array($missing));
96
+        $this->assertEquals($expectedMissing, $missing);
97
+    }
98 98
 
99
-	/**
100
-	 * @param $expectedMissing
101
-	 * @param $databases
102
-	 */
103
-	#[\PHPUnit\Framework\Attributes\DataProvider('providesDatabases')]
104
-	public function testDatabases($expectedMissing, $databases): void {
105
-		$app = [
106
-			'dependencies' => [
107
-			]
108
-		];
109
-		if (!is_null($databases)) {
110
-			$app['dependencies']['database'] = $databases;
111
-		}
112
-		$missing = $this->analyser->analyze($app);
99
+    /**
100
+     * @param $expectedMissing
101
+     * @param $databases
102
+     */
103
+    #[\PHPUnit\Framework\Attributes\DataProvider('providesDatabases')]
104
+    public function testDatabases($expectedMissing, $databases): void {
105
+        $app = [
106
+            'dependencies' => [
107
+            ]
108
+        ];
109
+        if (!is_null($databases)) {
110
+            $app['dependencies']['database'] = $databases;
111
+        }
112
+        $missing = $this->analyser->analyze($app);
113 113
 
114
-		$this->assertTrue(is_array($missing));
115
-		$this->assertEquals($expectedMissing, $missing);
116
-	}
114
+        $this->assertTrue(is_array($missing));
115
+        $this->assertEquals($expectedMissing, $missing);
116
+    }
117 117
 
118
-	/**
119
-	 *
120
-	 * @param string $expectedMissing
121
-	 * @param string|null $commands
122
-	 */
123
-	#[\PHPUnit\Framework\Attributes\DataProvider('providesCommands')]
124
-	public function testCommand($expectedMissing, $commands): void {
125
-		$app = [
126
-			'dependencies' => [
127
-			]
128
-		];
129
-		if (!is_null($commands)) {
130
-			$app['dependencies']['command'] = $commands;
131
-		}
132
-		$missing = $this->analyser->analyze($app);
118
+    /**
119
+     *
120
+     * @param string $expectedMissing
121
+     * @param string|null $commands
122
+     */
123
+    #[\PHPUnit\Framework\Attributes\DataProvider('providesCommands')]
124
+    public function testCommand($expectedMissing, $commands): void {
125
+        $app = [
126
+            'dependencies' => [
127
+            ]
128
+        ];
129
+        if (!is_null($commands)) {
130
+            $app['dependencies']['command'] = $commands;
131
+        }
132
+        $missing = $this->analyser->analyze($app);
133 133
 
134
-		$this->assertTrue(is_array($missing));
135
-		$this->assertEquals($expectedMissing, $missing);
136
-	}
134
+        $this->assertTrue(is_array($missing));
135
+        $this->assertEquals($expectedMissing, $missing);
136
+    }
137 137
 
138
-	/**
139
-	 * @param $expectedMissing
140
-	 * @param $libs
141
-	 */
142
-	#[\PHPUnit\Framework\Attributes\DataProvider('providesLibs')]
143
-	public function testLibs($expectedMissing, $libs): void {
144
-		$app = [
145
-			'dependencies' => [
146
-			]
147
-		];
148
-		if (!is_null($libs)) {
149
-			$app['dependencies']['lib'] = $libs;
150
-		}
138
+    /**
139
+     * @param $expectedMissing
140
+     * @param $libs
141
+     */
142
+    #[\PHPUnit\Framework\Attributes\DataProvider('providesLibs')]
143
+    public function testLibs($expectedMissing, $libs): void {
144
+        $app = [
145
+            'dependencies' => [
146
+            ]
147
+        ];
148
+        if (!is_null($libs)) {
149
+            $app['dependencies']['lib'] = $libs;
150
+        }
151 151
 
152
-		$missing = $this->analyser->analyze($app);
152
+        $missing = $this->analyser->analyze($app);
153 153
 
154
-		$this->assertTrue(is_array($missing));
155
-		$this->assertEquals($expectedMissing, $missing);
156
-	}
154
+        $this->assertTrue(is_array($missing));
155
+        $this->assertEquals($expectedMissing, $missing);
156
+    }
157 157
 
158
-	/**
159
-	 * @param $expectedMissing
160
-	 * @param $oss
161
-	 */
162
-	#[\PHPUnit\Framework\Attributes\DataProvider('providesOS')]
163
-	public function testOS($expectedMissing, $oss): void {
164
-		$app = [
165
-			'dependencies' => []
166
-		];
167
-		if (!is_null($oss)) {
168
-			$app['dependencies']['os'] = $oss;
169
-		}
158
+    /**
159
+     * @param $expectedMissing
160
+     * @param $oss
161
+     */
162
+    #[\PHPUnit\Framework\Attributes\DataProvider('providesOS')]
163
+    public function testOS($expectedMissing, $oss): void {
164
+        $app = [
165
+            'dependencies' => []
166
+        ];
167
+        if (!is_null($oss)) {
168
+            $app['dependencies']['os'] = $oss;
169
+        }
170 170
 
171
-		$missing = $this->analyser->analyze($app);
171
+        $missing = $this->analyser->analyze($app);
172 172
 
173
-		$this->assertTrue(is_array($missing));
174
-		$this->assertEquals($expectedMissing, $missing);
175
-	}
173
+        $this->assertTrue(is_array($missing));
174
+        $this->assertEquals($expectedMissing, $missing);
175
+    }
176 176
 
177
-	/**
178
-	 * @param $expectedMissing
179
-	 * @param $oc
180
-	 */
181
-	#[\PHPUnit\Framework\Attributes\DataProvider('providesOC')]
182
-	public function testOC($expectedMissing, $oc): void {
183
-		$app = [
184
-			'dependencies' => []
185
-		];
186
-		if (!is_null($oc)) {
187
-			$app['dependencies'] = $oc;
188
-		}
177
+    /**
178
+     * @param $expectedMissing
179
+     * @param $oc
180
+     */
181
+    #[\PHPUnit\Framework\Attributes\DataProvider('providesOC')]
182
+    public function testOC($expectedMissing, $oc): void {
183
+        $app = [
184
+            'dependencies' => []
185
+        ];
186
+        if (!is_null($oc)) {
187
+            $app['dependencies'] = $oc;
188
+        }
189 189
 
190
-		$missing = $this->analyser->analyze($app);
190
+        $missing = $this->analyser->analyze($app);
191 191
 
192
-		$this->assertTrue(is_array($missing));
193
-		$this->assertEquals($expectedMissing, $missing);
194
-	}
192
+        $this->assertTrue(is_array($missing));
193
+        $this->assertEquals($expectedMissing, $missing);
194
+    }
195 195
 
196
-	/**
197
-	 * @return array
198
-	 */
199
-	public static function providesOC(): array {
200
-		return [
201
-			// no version -> no missing dependency
202
-			[
203
-				[],
204
-				null,
205
-			],
206
-			[
207
-				[],
208
-				[
209
-					'nextcloud' => [
210
-						'@attributes' => [
211
-							'min-version' => '8',
212
-							'max-version' => '8',
213
-						],
214
-					],
215
-				],
216
-			],
217
-			[
218
-				[],
219
-				[
220
-					'nextcloud' => [
221
-						'@attributes' => [
222
-							'min-version' => '8.0',
223
-							'max-version' => '8.0',
224
-						],
225
-					],
226
-				],
227
-			],
228
-			[
229
-				[],
230
-				[
231
-					'nextcloud' => [
232
-						'@attributes' => [
233
-							'min-version' => '8.0.2',
234
-							'max-version' => '8.0.2'
235
-						],
236
-					],
237
-				],
238
-			],
239
-			[
240
-				[
241
-					'Server version 8.0.3 or higher is required.',
242
-				],
243
-				[
244
-					'nextcloud' => [
245
-						'@attributes' => [
246
-							'min-version' => '8.0.3'
247
-						],
248
-					],
249
-				],
250
-			],
251
-			[
252
-				[
253
-					'Server version 9 or higher is required.',
254
-				],
255
-				[
256
-					'nextcloud' => [
257
-						'@attributes' => [
258
-							'min-version' => '9'
259
-						],
260
-					],
261
-				],
262
-			],
263
-			[
264
-				[
265
-					'Server version 10 or higher is required.',
266
-				],
267
-				[
268
-					'nextcloud' => [
269
-						'@attributes' => [
270
-							'min-version' => '10'
271
-						],
272
-					],
273
-					'owncloud' => [
274
-						'@attributes' => [
275
-							'min-version' => '9'
276
-						],
277
-					],
278
-				],
279
-			],
280
-			[
281
-				[
282
-					'Server version 9.2 or higher is required.',
283
-				],
284
-				[
285
-					'nextcloud' => [
286
-						'@attributes' => [
287
-							'min-version' => '9.2',
288
-						],
289
-					],
290
-				],
291
-			],
292
-			[
293
-				[
294
-					'Server version 11 or higher is required.',
295
-				],
296
-				[
297
-					'nextcloud' => [
298
-						'@attributes' => [
299
-							'min-version' => '11',
300
-						],
301
-					],
302
-				],
303
-			],
304
-			[
305
-				[
306
-					'Server version 8.0.1 or lower is required.',
307
-				],
308
-				[
309
-					'nextcloud' => [
310
-						'@attributes' => [
311
-							'max-version' => '8.0.1',
312
-						],
313
-					],
314
-				],
315
-			],
316
-			[
317
-				[],
318
-				[
319
-					'owncloud' => [
320
-						'@attributes' => [
321
-							'min-version' => '8',
322
-							'max-version' => '8',
323
-						],
324
-					],
325
-				],
326
-			],
327
-			[
328
-				[],
329
-				[
330
-					'owncloud' => [
331
-						'@attributes' => [
332
-							'min-version' => '8.0',
333
-							'max-version' => '8.0',
334
-						],
335
-					],
336
-				],
337
-			],
338
-			[
339
-				[],
340
-				[
341
-					'owncloud' => [
342
-						'@attributes' => [
343
-							'min-version' => '8.0.2',
344
-							'max-version' => '8.0.2'
345
-						],
346
-					],
347
-				],
348
-			],
349
-			[
350
-				[
351
-					'Server version 8.0.3 or higher is required.',
352
-				],
353
-				[
354
-					'owncloud' => [
355
-						'@attributes' => [
356
-							'min-version' => '8.0.3'
357
-						],
358
-					],
359
-				],
360
-			],
361
-			[
362
-				[
363
-					'Server version 9 or higher is required.',
364
-				],
365
-				[
366
-					'owncloud' => [
367
-						'@attributes' => [
368
-							'min-version' => '9'
369
-						],
370
-					],
371
-				],
372
-			],
373
-			[
374
-				[
375
-					'Server version 9.2 or higher is required.',
376
-				],
377
-				[
378
-					'owncloud' => [
379
-						'@attributes' => [
380
-							'min-version' => '9.2',
381
-						],
382
-					],
383
-				],
384
-			],
385
-			[
386
-				[
387
-					'Server version 8.0.1 or lower is required.',
388
-				],
389
-				[
390
-					'owncloud' => [
391
-						'@attributes' => [
392
-							'max-version' => '8.0.1',
393
-						],
394
-					],
395
-				],
396
-			],
397
-		];
398
-	}
196
+    /**
197
+     * @return array
198
+     */
199
+    public static function providesOC(): array {
200
+        return [
201
+            // no version -> no missing dependency
202
+            [
203
+                [],
204
+                null,
205
+            ],
206
+            [
207
+                [],
208
+                [
209
+                    'nextcloud' => [
210
+                        '@attributes' => [
211
+                            'min-version' => '8',
212
+                            'max-version' => '8',
213
+                        ],
214
+                    ],
215
+                ],
216
+            ],
217
+            [
218
+                [],
219
+                [
220
+                    'nextcloud' => [
221
+                        '@attributes' => [
222
+                            'min-version' => '8.0',
223
+                            'max-version' => '8.0',
224
+                        ],
225
+                    ],
226
+                ],
227
+            ],
228
+            [
229
+                [],
230
+                [
231
+                    'nextcloud' => [
232
+                        '@attributes' => [
233
+                            'min-version' => '8.0.2',
234
+                            'max-version' => '8.0.2'
235
+                        ],
236
+                    ],
237
+                ],
238
+            ],
239
+            [
240
+                [
241
+                    'Server version 8.0.3 or higher is required.',
242
+                ],
243
+                [
244
+                    'nextcloud' => [
245
+                        '@attributes' => [
246
+                            'min-version' => '8.0.3'
247
+                        ],
248
+                    ],
249
+                ],
250
+            ],
251
+            [
252
+                [
253
+                    'Server version 9 or higher is required.',
254
+                ],
255
+                [
256
+                    'nextcloud' => [
257
+                        '@attributes' => [
258
+                            'min-version' => '9'
259
+                        ],
260
+                    ],
261
+                ],
262
+            ],
263
+            [
264
+                [
265
+                    'Server version 10 or higher is required.',
266
+                ],
267
+                [
268
+                    'nextcloud' => [
269
+                        '@attributes' => [
270
+                            'min-version' => '10'
271
+                        ],
272
+                    ],
273
+                    'owncloud' => [
274
+                        '@attributes' => [
275
+                            'min-version' => '9'
276
+                        ],
277
+                    ],
278
+                ],
279
+            ],
280
+            [
281
+                [
282
+                    'Server version 9.2 or higher is required.',
283
+                ],
284
+                [
285
+                    'nextcloud' => [
286
+                        '@attributes' => [
287
+                            'min-version' => '9.2',
288
+                        ],
289
+                    ],
290
+                ],
291
+            ],
292
+            [
293
+                [
294
+                    'Server version 11 or higher is required.',
295
+                ],
296
+                [
297
+                    'nextcloud' => [
298
+                        '@attributes' => [
299
+                            'min-version' => '11',
300
+                        ],
301
+                    ],
302
+                ],
303
+            ],
304
+            [
305
+                [
306
+                    'Server version 8.0.1 or lower is required.',
307
+                ],
308
+                [
309
+                    'nextcloud' => [
310
+                        '@attributes' => [
311
+                            'max-version' => '8.0.1',
312
+                        ],
313
+                    ],
314
+                ],
315
+            ],
316
+            [
317
+                [],
318
+                [
319
+                    'owncloud' => [
320
+                        '@attributes' => [
321
+                            'min-version' => '8',
322
+                            'max-version' => '8',
323
+                        ],
324
+                    ],
325
+                ],
326
+            ],
327
+            [
328
+                [],
329
+                [
330
+                    'owncloud' => [
331
+                        '@attributes' => [
332
+                            'min-version' => '8.0',
333
+                            'max-version' => '8.0',
334
+                        ],
335
+                    ],
336
+                ],
337
+            ],
338
+            [
339
+                [],
340
+                [
341
+                    'owncloud' => [
342
+                        '@attributes' => [
343
+                            'min-version' => '8.0.2',
344
+                            'max-version' => '8.0.2'
345
+                        ],
346
+                    ],
347
+                ],
348
+            ],
349
+            [
350
+                [
351
+                    'Server version 8.0.3 or higher is required.',
352
+                ],
353
+                [
354
+                    'owncloud' => [
355
+                        '@attributes' => [
356
+                            'min-version' => '8.0.3'
357
+                        ],
358
+                    ],
359
+                ],
360
+            ],
361
+            [
362
+                [
363
+                    'Server version 9 or higher is required.',
364
+                ],
365
+                [
366
+                    'owncloud' => [
367
+                        '@attributes' => [
368
+                            'min-version' => '9'
369
+                        ],
370
+                    ],
371
+                ],
372
+            ],
373
+            [
374
+                [
375
+                    'Server version 9.2 or higher is required.',
376
+                ],
377
+                [
378
+                    'owncloud' => [
379
+                        '@attributes' => [
380
+                            'min-version' => '9.2',
381
+                        ],
382
+                    ],
383
+                ],
384
+            ],
385
+            [
386
+                [
387
+                    'Server version 8.0.1 or lower is required.',
388
+                ],
389
+                [
390
+                    'owncloud' => [
391
+                        '@attributes' => [
392
+                            'max-version' => '8.0.1',
393
+                        ],
394
+                    ],
395
+                ],
396
+            ],
397
+        ];
398
+    }
399 399
 
400
-	/**
401
-	 * @return array
402
-	 */
403
-	public static function providesOS(): array {
404
-		return [
405
-			[[], null],
406
-			[[], []],
407
-			[['The following platforms are supported: ANDROID'], 'ANDROID'],
408
-			[['The following platforms are supported: WINNT'], ['WINNT']]
409
-		];
410
-	}
400
+    /**
401
+     * @return array
402
+     */
403
+    public static function providesOS(): array {
404
+        return [
405
+            [[], null],
406
+            [[], []],
407
+            [['The following platforms are supported: ANDROID'], 'ANDROID'],
408
+            [['The following platforms are supported: WINNT'], ['WINNT']]
409
+        ];
410
+    }
411 411
 
412
-	/**
413
-	 * @return array
414
-	 */
415
-	public static function providesLibs(): array {
416
-		return [
417
-			// we expect curl to exist
418
-			[[], 'curl'],
419
-			// we expect abcde to exist
420
-			[['The library abcde is not available.'], ['abcde']],
421
-			// curl in version 100.0 does not exist
422
-			[['Library curl with a version higher than 100.0 is required - available version 2.3.4.'],
423
-				[['@attributes' => ['min-version' => '100.0'], '@value' => 'curl']]],
424
-			// curl in version 100.0 does not exist
425
-			[['Library curl with a version lower than 1.0.0 is required - available version 2.3.4.'],
426
-				[['@attributes' => ['max-version' => '1.0.0'], '@value' => 'curl']]],
427
-			[['Library curl with a version lower than 2.3.3 is required - available version 2.3.4.'],
428
-				[['@attributes' => ['max-version' => '2.3.3'], '@value' => 'curl']]],
429
-			[['Library curl with a version higher than 2.3.5 is required - available version 2.3.4.'],
430
-				[['@attributes' => ['min-version' => '2.3.5'], '@value' => 'curl']]],
431
-			[[],
432
-				[['@attributes' => ['min-version' => '2.3.4', 'max-version' => '2.3.4'], '@value' => 'curl']]],
433
-			[[],
434
-				[['@attributes' => ['min-version' => '2.3', 'max-version' => '2.3'], '@value' => 'curl']]],
435
-			[[],
436
-				[['@attributes' => ['min-version' => '2', 'max-version' => '2'], '@value' => 'curl']]],
437
-			[[],
438
-				['@attributes' => ['min-version' => '2', 'max-version' => '2'], '@value' => 'curl']],
439
-		];
440
-	}
412
+    /**
413
+     * @return array
414
+     */
415
+    public static function providesLibs(): array {
416
+        return [
417
+            // we expect curl to exist
418
+            [[], 'curl'],
419
+            // we expect abcde to exist
420
+            [['The library abcde is not available.'], ['abcde']],
421
+            // curl in version 100.0 does not exist
422
+            [['Library curl with a version higher than 100.0 is required - available version 2.3.4.'],
423
+                [['@attributes' => ['min-version' => '100.0'], '@value' => 'curl']]],
424
+            // curl in version 100.0 does not exist
425
+            [['Library curl with a version lower than 1.0.0 is required - available version 2.3.4.'],
426
+                [['@attributes' => ['max-version' => '1.0.0'], '@value' => 'curl']]],
427
+            [['Library curl with a version lower than 2.3.3 is required - available version 2.3.4.'],
428
+                [['@attributes' => ['max-version' => '2.3.3'], '@value' => 'curl']]],
429
+            [['Library curl with a version higher than 2.3.5 is required - available version 2.3.4.'],
430
+                [['@attributes' => ['min-version' => '2.3.5'], '@value' => 'curl']]],
431
+            [[],
432
+                [['@attributes' => ['min-version' => '2.3.4', 'max-version' => '2.3.4'], '@value' => 'curl']]],
433
+            [[],
434
+                [['@attributes' => ['min-version' => '2.3', 'max-version' => '2.3'], '@value' => 'curl']]],
435
+            [[],
436
+                [['@attributes' => ['min-version' => '2', 'max-version' => '2'], '@value' => 'curl']]],
437
+            [[],
438
+                ['@attributes' => ['min-version' => '2', 'max-version' => '2'], '@value' => 'curl']],
439
+        ];
440
+    }
441 441
 
442
-	/**
443
-	 * @return array
444
-	 */
445
-	public static function providesCommands(): array {
446
-		return [
447
-			[[], null],
448
-			// grep is known on linux
449
-			[[], [['@attributes' => ['os' => 'Linux'], '@value' => 'grep']]],
450
-			// grepp is not known on linux
451
-			[['The command line tool grepp could not be found'], [['@attributes' => ['os' => 'Linux'], '@value' => 'grepp']]],
452
-			// we don't care about tools on Windows - we are on Linux
453
-			[[], [['@attributes' => ['os' => 'Windows'], '@value' => 'grepp']]],
454
-			// grep is known on all systems
455
-			[[], 'grep'],
456
-			[[], ['@attributes' => ['os' => 'Linux'], '@value' => 'grep']],
457
-		];
458
-	}
442
+    /**
443
+     * @return array
444
+     */
445
+    public static function providesCommands(): array {
446
+        return [
447
+            [[], null],
448
+            // grep is known on linux
449
+            [[], [['@attributes' => ['os' => 'Linux'], '@value' => 'grep']]],
450
+            // grepp is not known on linux
451
+            [['The command line tool grepp could not be found'], [['@attributes' => ['os' => 'Linux'], '@value' => 'grepp']]],
452
+            // we don't care about tools on Windows - we are on Linux
453
+            [[], [['@attributes' => ['os' => 'Windows'], '@value' => 'grepp']]],
454
+            // grep is known on all systems
455
+            [[], 'grep'],
456
+            [[], ['@attributes' => ['os' => 'Linux'], '@value' => 'grep']],
457
+        ];
458
+    }
459 459
 
460
-	/**
461
-	 * @return array
462
-	 */
463
-	public static function providesDatabases(): array {
464
-		return [
465
-			// non BC - in case on databases are defined -> all are supported
466
-			[[], null],
467
-			[[], []],
468
-			[['The following databases are supported: mongodb'], 'mongodb'],
469
-			[['The following databases are supported: sqlite, postgres'], ['sqlite', ['@value' => 'postgres']]],
470
-		];
471
-	}
460
+    /**
461
+     * @return array
462
+     */
463
+    public static function providesDatabases(): array {
464
+        return [
465
+            // non BC - in case on databases are defined -> all are supported
466
+            [[], null],
467
+            [[], []],
468
+            [['The following databases are supported: mongodb'], 'mongodb'],
469
+            [['The following databases are supported: sqlite, postgres'], ['sqlite', ['@value' => 'postgres']]],
470
+        ];
471
+    }
472 472
 
473
-	/**
474
-	 * @return array
475
-	 */
476
-	public static function providesPhpVersion(): array {
477
-		return [
478
-			[[], null, null, null],
479
-			[[], '5.4', null, null],
480
-			[[], null, '5.5', null],
481
-			[[], '5.4', '5.5', null],
482
-			[['PHP 5.4.4 or higher is required.'], '5.4.4', null, null],
483
-			[['PHP with a version lower than 5.4.2 is required.'], null, '5.4.2', null],
484
-			[['64bit or higher PHP required.'], null, null, 64],
485
-			[[], '5.4', '5.4', null],
486
-		];
487
-	}
473
+    /**
474
+     * @return array
475
+     */
476
+    public static function providesPhpVersion(): array {
477
+        return [
478
+            [[], null, null, null],
479
+            [[], '5.4', null, null],
480
+            [[], null, '5.5', null],
481
+            [[], '5.4', '5.5', null],
482
+            [['PHP 5.4.4 or higher is required.'], '5.4.4', null, null],
483
+            [['PHP with a version lower than 5.4.2 is required.'], null, '5.4.2', null],
484
+            [['64bit or higher PHP required.'], null, null, 64],
485
+            [[], '5.4', '5.4', null],
486
+        ];
487
+    }
488 488
 }
Please login to merge, or discard this patch.
lib/private/App/AppManager.php 1 patch
Indentation   +914 added lines, -914 removed lines patch added patch discarded remove patch
@@ -35,918 +35,918 @@
 block discarded – undo
35 35
 use Psr\Log\LoggerInterface;
36 36
 
37 37
 class AppManager implements IAppManager {
38
-	/**
39
-	 * Apps with these types can not be enabled for certain groups only
40
-	 * @var string[]
41
-	 */
42
-	protected $protectedAppTypes = [
43
-		'filesystem',
44
-		'prelogin',
45
-		'authentication',
46
-		'logging',
47
-		'prevent_group_restriction',
48
-	];
49
-
50
-	/** @var string[] $appId => $enabled */
51
-	private array $enabledAppsCache = [];
52
-
53
-	/** @var string[]|null */
54
-	private ?array $shippedApps = null;
55
-
56
-	private array $alwaysEnabled = [];
57
-	private array $defaultEnabled = [];
58
-
59
-	/** @var array */
60
-	private array $appInfos = [];
61
-
62
-	/** @var array */
63
-	private array $appVersions = [];
64
-
65
-	/** @var array */
66
-	private array $autoDisabledApps = [];
67
-	private array $appTypes = [];
68
-
69
-	/** @var array<string, true> */
70
-	private array $loadedApps = [];
71
-
72
-	private ?AppConfig $appConfig = null;
73
-	private ?IURLGenerator $urlGenerator = null;
74
-	private ?INavigationManager $navigationManager = null;
75
-
76
-	/**
77
-	 * Be extremely careful when injecting classes here. The AppManager is used by the installer,
78
-	 * so it needs to work before installation. See how AppConfig and IURLGenerator are injected for reference
79
-	 */
80
-	public function __construct(
81
-		private IUserSession $userSession,
82
-		private IConfig $config,
83
-		private IGroupManager $groupManager,
84
-		private ICacheFactory $memCacheFactory,
85
-		private IEventDispatcher $dispatcher,
86
-		private LoggerInterface $logger,
87
-		private ServerVersion $serverVersion,
88
-		private ConfigManager $configManager,
89
-	) {
90
-	}
91
-
92
-	private function getNavigationManager(): INavigationManager {
93
-		if ($this->navigationManager === null) {
94
-			$this->navigationManager = Server::get(INavigationManager::class);
95
-		}
96
-		return $this->navigationManager;
97
-	}
98
-
99
-	public function getAppIcon(string $appId, bool $dark = false): ?string {
100
-		$possibleIcons = $dark ? [$appId . '-dark.svg', 'app-dark.svg'] : [$appId . '.svg', 'app.svg'];
101
-		$icon = null;
102
-		foreach ($possibleIcons as $iconName) {
103
-			try {
104
-				$icon = $this->getUrlGenerator()->imagePath($appId, $iconName);
105
-				break;
106
-			} catch (\RuntimeException $e) {
107
-				// ignore
108
-			}
109
-		}
110
-		return $icon;
111
-	}
112
-
113
-	private function getAppConfig(): AppConfig {
114
-		if ($this->appConfig !== null) {
115
-			return $this->appConfig;
116
-		}
117
-		if (!$this->config->getSystemValueBool('installed', false)) {
118
-			throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
119
-		}
120
-		$this->appConfig = Server::get(AppConfig::class);
121
-		return $this->appConfig;
122
-	}
123
-
124
-	private function getUrlGenerator(): IURLGenerator {
125
-		if ($this->urlGenerator !== null) {
126
-			return $this->urlGenerator;
127
-		}
128
-		if (!$this->config->getSystemValueBool('installed', false)) {
129
-			throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
130
-		}
131
-		$this->urlGenerator = Server::get(IURLGenerator::class);
132
-		return $this->urlGenerator;
133
-	}
134
-
135
-	/**
136
-	 * For all enabled apps, return the value of their 'enabled' config key.
137
-	 *
138
-	 * @return array<string,string> appId => enabled (may be 'yes', or a json encoded list of group ids)
139
-	 */
140
-	private function getEnabledAppsValues(): array {
141
-		if (!$this->enabledAppsCache) {
142
-			/** @var array<string,string> */
143
-			$values = $this->getAppConfig()->searchValues('enabled', false, IAppConfig::VALUE_STRING);
144
-
145
-			$alwaysEnabledApps = $this->getAlwaysEnabledApps();
146
-			foreach ($alwaysEnabledApps as $appId) {
147
-				$values[$appId] = 'yes';
148
-			}
149
-
150
-			$this->enabledAppsCache = array_filter($values, function ($value) {
151
-				return $value !== 'no';
152
-			});
153
-			ksort($this->enabledAppsCache);
154
-		}
155
-		return $this->enabledAppsCache;
156
-	}
157
-
158
-	/**
159
-	 * Deprecated alias
160
-	 *
161
-	 * @return string[]
162
-	 */
163
-	public function getInstalledApps() {
164
-		return $this->getEnabledApps();
165
-	}
166
-
167
-	/**
168
-	 * List all enabled apps, either for everyone or for some groups
169
-	 *
170
-	 * @return list<string>
171
-	 */
172
-	public function getEnabledApps(): array {
173
-		return array_keys($this->getEnabledAppsValues());
174
-	}
175
-
176
-	/**
177
-	 * Get a list of all apps in the apps folder
178
-	 *
179
-	 * @return list<string> an array of app names (string IDs)
180
-	 */
181
-	public function getAllAppsInAppsFolders(): array {
182
-		$apps = [];
183
-
184
-		foreach (\OC::$APPSROOTS as $apps_dir) {
185
-			if (!is_readable($apps_dir['path'])) {
186
-				$this->logger->warning('unable to read app folder : ' . $apps_dir['path'], ['app' => 'core']);
187
-				continue;
188
-			}
189
-			$dh = opendir($apps_dir['path']);
190
-
191
-			if (is_resource($dh)) {
192
-				while (($file = readdir($dh)) !== false) {
193
-					if (
194
-						$file[0] != '.'
195
-						&& is_dir($apps_dir['path'] . '/' . $file)
196
-						&& is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')
197
-					) {
198
-						$apps[] = $file;
199
-					}
200
-				}
201
-			}
202
-		}
203
-
204
-		return array_values(array_unique($apps));
205
-	}
206
-
207
-	/**
208
-	 * List all apps enabled for a user
209
-	 *
210
-	 * @param \OCP\IUser $user
211
-	 * @return list<string>
212
-	 */
213
-	public function getEnabledAppsForUser(IUser $user) {
214
-		$apps = $this->getEnabledAppsValues();
215
-		$appsForUser = array_filter($apps, function ($enabled) use ($user) {
216
-			return $this->checkAppForUser($enabled, $user);
217
-		});
218
-		return array_keys($appsForUser);
219
-	}
220
-
221
-	public function getEnabledAppsForGroup(IGroup $group): array {
222
-		$apps = $this->getEnabledAppsValues();
223
-		$appsForGroups = array_filter($apps, function ($enabled) use ($group) {
224
-			return $this->checkAppForGroups($enabled, $group);
225
-		});
226
-		return array_keys($appsForGroups);
227
-	}
228
-
229
-	/**
230
-	 * Loads all apps
231
-	 *
232
-	 * @param string[] $types
233
-	 * @return bool
234
-	 *
235
-	 * This function walks through the Nextcloud directory and loads all apps
236
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
237
-	 * exists.
238
-	 *
239
-	 * if $types is set to non-empty array, only apps of those types will be loaded
240
-	 */
241
-	public function loadApps(array $types = []): bool {
242
-		if ($this->config->getSystemValueBool('maintenance', false)) {
243
-			return false;
244
-		}
245
-		// Load the enabled apps here
246
-		$apps = \OC_App::getEnabledApps();
247
-
248
-		// Add each apps' folder as allowed class path
249
-		foreach ($apps as $app) {
250
-			// If the app is already loaded then autoloading it makes no sense
251
-			if (!$this->isAppLoaded($app)) {
252
-				$path = \OC_App::getAppPath($app);
253
-				if ($path !== false) {
254
-					\OC_App::registerAutoloading($app, $path);
255
-				}
256
-			}
257
-		}
258
-
259
-		// prevent app loading from printing output
260
-		ob_start();
261
-		foreach ($apps as $app) {
262
-			if (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))) {
263
-				try {
264
-					$this->loadApp($app);
265
-				} catch (\Throwable $e) {
266
-					$this->logger->emergency('Error during app loading: ' . $e->getMessage(), [
267
-						'exception' => $e,
268
-						'app' => $app,
269
-					]);
270
-				}
271
-			}
272
-		}
273
-		ob_end_clean();
274
-
275
-		return true;
276
-	}
277
-
278
-	/**
279
-	 * check if an app is of a specific type
280
-	 *
281
-	 * @param string $app
282
-	 * @param array $types
283
-	 * @return bool
284
-	 */
285
-	public function isType(string $app, array $types): bool {
286
-		$appTypes = $this->getAppTypes($app);
287
-		foreach ($types as $type) {
288
-			if (in_array($type, $appTypes, true)) {
289
-				return true;
290
-			}
291
-		}
292
-		return false;
293
-	}
294
-
295
-	/**
296
-	 * get the types of an app
297
-	 *
298
-	 * @param string $app
299
-	 * @return string[]
300
-	 */
301
-	private function getAppTypes(string $app): array {
302
-		//load the cache
303
-		if (count($this->appTypes) === 0) {
304
-			$this->appTypes = $this->getAppConfig()->getValues(false, 'types') ?: [];
305
-		}
306
-
307
-		if (isset($this->appTypes[$app])) {
308
-			return explode(',', $this->appTypes[$app]);
309
-		}
310
-
311
-		return [];
312
-	}
313
-
314
-	/**
315
-	 * @return array
316
-	 */
317
-	public function getAutoDisabledApps(): array {
318
-		return $this->autoDisabledApps;
319
-	}
320
-
321
-	public function getAppRestriction(string $appId): array {
322
-		$values = $this->getEnabledAppsValues();
323
-
324
-		if (!isset($values[$appId])) {
325
-			return [];
326
-		}
327
-
328
-		if ($values[$appId] === 'yes' || $values[$appId] === 'no') {
329
-			return [];
330
-		}
331
-		return json_decode($values[$appId], true);
332
-	}
333
-
334
-	/**
335
-	 * Check if an app is enabled for user
336
-	 *
337
-	 * @param string $appId
338
-	 * @param \OCP\IUser|null $user (optional) if not defined, the currently logged in user will be used
339
-	 * @return bool
340
-	 */
341
-	public function isEnabledForUser($appId, $user = null) {
342
-		if ($this->isAlwaysEnabled($appId)) {
343
-			return true;
344
-		}
345
-		if ($user === null) {
346
-			$user = $this->userSession->getUser();
347
-		}
348
-		$enabledAppsValues = $this->getEnabledAppsValues();
349
-		if (isset($enabledAppsValues[$appId])) {
350
-			return $this->checkAppForUser($enabledAppsValues[$appId], $user);
351
-		} else {
352
-			return false;
353
-		}
354
-	}
355
-
356
-	private function checkAppForUser(string $enabled, ?IUser $user): bool {
357
-		if ($enabled === 'yes') {
358
-			return true;
359
-		} elseif ($user === null) {
360
-			return false;
361
-		} else {
362
-			if (empty($enabled)) {
363
-				return false;
364
-			}
365
-
366
-			$groupIds = json_decode($enabled);
367
-
368
-			if (!is_array($groupIds)) {
369
-				$jsonError = json_last_error();
370
-				$jsonErrorMsg = json_last_error_msg();
371
-				// 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)
372
-				$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);
373
-				return false;
374
-			}
375
-
376
-			$userGroups = $this->groupManager->getUserGroupIds($user);
377
-			foreach ($userGroups as $groupId) {
378
-				if (in_array($groupId, $groupIds, true)) {
379
-					return true;
380
-				}
381
-			}
382
-			return false;
383
-		}
384
-	}
385
-
386
-	private function checkAppForGroups(string $enabled, IGroup $group): bool {
387
-		if ($enabled === 'yes') {
388
-			return true;
389
-		} else {
390
-			if (empty($enabled)) {
391
-				return false;
392
-			}
393
-
394
-			$groupIds = json_decode($enabled);
395
-
396
-			if (!is_array($groupIds)) {
397
-				$jsonError = json_last_error();
398
-				$jsonErrorMsg = json_last_error_msg();
399
-				// 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)
400
-				$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);
401
-				return false;
402
-			}
403
-
404
-			return in_array($group->getGID(), $groupIds);
405
-		}
406
-	}
407
-
408
-	/**
409
-	 * Check if an app is enabled in the instance
410
-	 *
411
-	 * Notice: This actually checks if the app is enabled and not only if it is installed.
412
-	 *
413
-	 * @param string $appId
414
-	 */
415
-	public function isInstalled($appId): bool {
416
-		return $this->isEnabledForAnyone($appId);
417
-	}
418
-
419
-	public function isEnabledForAnyone(string $appId): bool {
420
-		$enabledAppsValues = $this->getEnabledAppsValues();
421
-		return isset($enabledAppsValues[$appId]);
422
-	}
423
-
424
-	/**
425
-	 * Overwrite the `max-version` requirement for this app.
426
-	 */
427
-	public function overwriteNextcloudRequirement(string $appId): void {
428
-		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
429
-		if (!in_array($appId, $ignoreMaxApps, true)) {
430
-			$ignoreMaxApps[] = $appId;
431
-		}
432
-		$this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
433
-	}
434
-
435
-	/**
436
-	 * Remove the `max-version` overwrite for this app.
437
-	 * This means this app now again can not be enabled if the `max-version` is smaller than the current Nextcloud version.
438
-	 */
439
-	public function removeOverwriteNextcloudRequirement(string $appId): void {
440
-		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
441
-		$ignoreMaxApps = array_filter($ignoreMaxApps, fn (string $id) => $id !== $appId);
442
-		$this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
443
-	}
444
-
445
-	public function loadApp(string $app): void {
446
-		if (isset($this->loadedApps[$app])) {
447
-			return;
448
-		}
449
-		$this->loadedApps[$app] = true;
450
-		$appPath = \OC_App::getAppPath($app);
451
-		if ($appPath === false) {
452
-			return;
453
-		}
454
-		$eventLogger = \OC::$server->get(IEventLogger::class);
455
-		$eventLogger->start("bootstrap:load_app:$app", "Load app: $app");
456
-
457
-		// in case someone calls loadApp() directly
458
-		\OC_App::registerAutoloading($app, $appPath);
459
-
460
-		if (is_file($appPath . '/appinfo/app.php')) {
461
-			$this->logger->error('/appinfo/app.php is not supported anymore, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
462
-				'app' => $app,
463
-			]);
464
-		}
465
-
466
-		$coordinator = Server::get(Coordinator::class);
467
-		$coordinator->bootApp($app);
468
-
469
-		$eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it");
470
-		$info = $this->getAppInfo($app);
471
-		if (!empty($info['activity'])) {
472
-			$activityManager = \OC::$server->get(IActivityManager::class);
473
-			if (!empty($info['activity']['filters'])) {
474
-				foreach ($info['activity']['filters'] as $filter) {
475
-					$activityManager->registerFilter($filter);
476
-				}
477
-			}
478
-			if (!empty($info['activity']['settings'])) {
479
-				foreach ($info['activity']['settings'] as $setting) {
480
-					$activityManager->registerSetting($setting);
481
-				}
482
-			}
483
-			if (!empty($info['activity']['providers'])) {
484
-				foreach ($info['activity']['providers'] as $provider) {
485
-					$activityManager->registerProvider($provider);
486
-				}
487
-			}
488
-		}
489
-
490
-		if (!empty($info['settings'])) {
491
-			$settingsManager = \OC::$server->get(ISettingsManager::class);
492
-			if (!empty($info['settings']['admin'])) {
493
-				foreach ($info['settings']['admin'] as $setting) {
494
-					$settingsManager->registerSetting('admin', $setting);
495
-				}
496
-			}
497
-			if (!empty($info['settings']['admin-section'])) {
498
-				foreach ($info['settings']['admin-section'] as $section) {
499
-					$settingsManager->registerSection('admin', $section);
500
-				}
501
-			}
502
-			if (!empty($info['settings']['personal'])) {
503
-				foreach ($info['settings']['personal'] as $setting) {
504
-					$settingsManager->registerSetting('personal', $setting);
505
-				}
506
-			}
507
-			if (!empty($info['settings']['personal-section'])) {
508
-				foreach ($info['settings']['personal-section'] as $section) {
509
-					$settingsManager->registerSection('personal', $section);
510
-				}
511
-			}
512
-		}
513
-
514
-		if (!empty($info['collaboration']['plugins'])) {
515
-			// deal with one or many plugin entries
516
-			$plugins = isset($info['collaboration']['plugins']['plugin']['@value'])
517
-				? [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
518
-			$collaboratorSearch = null;
519
-			$autoCompleteManager = null;
520
-			foreach ($plugins as $plugin) {
521
-				if ($plugin['@attributes']['type'] === 'collaborator-search') {
522
-					$pluginInfo = [
523
-						'shareType' => $plugin['@attributes']['share-type'],
524
-						'class' => $plugin['@value'],
525
-					];
526
-					$collaboratorSearch ??= \OC::$server->get(ICollaboratorSearch::class);
527
-					$collaboratorSearch->registerPlugin($pluginInfo);
528
-				} elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
529
-					$autoCompleteManager ??= \OC::$server->get(IAutoCompleteManager::class);
530
-					$autoCompleteManager->registerSorter($plugin['@value']);
531
-				}
532
-			}
533
-		}
534
-		$eventLogger->end("bootstrap:load_app:$app:info");
535
-
536
-		$eventLogger->end("bootstrap:load_app:$app");
537
-	}
538
-
539
-	/**
540
-	 * Check if an app is loaded
541
-	 * @param string $app app id
542
-	 * @since 26.0.0
543
-	 */
544
-	public function isAppLoaded(string $app): bool {
545
-		return isset($this->loadedApps[$app]);
546
-	}
547
-
548
-	/**
549
-	 * Enable an app for every user
550
-	 *
551
-	 * @param string $appId
552
-	 * @param bool $forceEnable
553
-	 * @throws AppPathNotFoundException
554
-	 * @throws \InvalidArgumentException if the application is not installed yet
555
-	 */
556
-	public function enableApp(string $appId, bool $forceEnable = false): void {
557
-		// Check if app exists
558
-		$this->getAppPath($appId);
559
-
560
-		if ($this->config->getAppValue($appId, 'installed_version', '') === '') {
561
-			throw new \InvalidArgumentException("$appId is not installed, cannot be enabled.");
562
-		}
563
-
564
-		if ($forceEnable) {
565
-			$this->overwriteNextcloudRequirement($appId);
566
-		}
567
-
568
-		$this->enabledAppsCache[$appId] = 'yes';
569
-		$this->getAppConfig()->setValue($appId, 'enabled', 'yes');
570
-		$this->dispatcher->dispatchTyped(new AppEnableEvent($appId));
571
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
572
-			ManagerEvent::EVENT_APP_ENABLE, $appId
573
-		));
574
-		$this->clearAppsCache();
575
-
576
-		$this->configManager->migrateConfigLexiconKeys($appId);
577
-	}
578
-
579
-	/**
580
-	 * Whether a list of types contains a protected app type
581
-	 *
582
-	 * @param string[] $types
583
-	 * @return bool
584
-	 */
585
-	public function hasProtectedAppType($types) {
586
-		if (empty($types)) {
587
-			return false;
588
-		}
589
-
590
-		$protectedTypes = array_intersect($this->protectedAppTypes, $types);
591
-		return !empty($protectedTypes);
592
-	}
593
-
594
-	/**
595
-	 * Enable an app only for specific groups
596
-	 *
597
-	 * @param string $appId
598
-	 * @param IGroup[] $groups
599
-	 * @param bool $forceEnable
600
-	 * @throws \InvalidArgumentException if app can't be enabled for groups
601
-	 * @throws AppPathNotFoundException
602
-	 */
603
-	public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void {
604
-		// Check if app exists
605
-		$this->getAppPath($appId);
606
-
607
-		$info = $this->getAppInfo($appId);
608
-		if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) {
609
-			throw new \InvalidArgumentException("$appId can't be enabled for groups.");
610
-		}
611
-
612
-		if ($this->config->getAppValue($appId, 'installed_version', '') === '') {
613
-			throw new \InvalidArgumentException("$appId is not installed, cannot be enabled.");
614
-		}
615
-
616
-		if ($forceEnable) {
617
-			$this->overwriteNextcloudRequirement($appId);
618
-		}
619
-
620
-		/** @var string[] $groupIds */
621
-		$groupIds = array_map(function ($group) {
622
-			/** @var IGroup $group */
623
-			return ($group instanceof IGroup)
624
-				? $group->getGID()
625
-				: $group;
626
-		}, $groups);
627
-
628
-		$this->enabledAppsCache[$appId] = json_encode($groupIds);
629
-		$this->getAppConfig()->setValue($appId, 'enabled', json_encode($groupIds));
630
-		$this->dispatcher->dispatchTyped(new AppEnableEvent($appId, $groupIds));
631
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
632
-			ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
633
-		));
634
-		$this->clearAppsCache();
635
-
636
-		$this->configManager->migrateConfigLexiconKeys($appId);
637
-	}
638
-
639
-	/**
640
-	 * Disable an app for every user
641
-	 *
642
-	 * @param string $appId
643
-	 * @param bool $automaticDisabled
644
-	 * @throws \Exception if app can't be disabled
645
-	 */
646
-	public function disableApp($appId, $automaticDisabled = false): void {
647
-		if ($this->isAlwaysEnabled($appId)) {
648
-			throw new \Exception("$appId can't be disabled.");
649
-		}
650
-
651
-		if ($automaticDisabled) {
652
-			$previousSetting = $this->getAppConfig()->getValue($appId, 'enabled', 'yes');
653
-			if ($previousSetting !== 'yes' && $previousSetting !== 'no') {
654
-				$previousSetting = json_decode($previousSetting, true);
655
-			}
656
-			$this->autoDisabledApps[$appId] = $previousSetting;
657
-		}
658
-
659
-		unset($this->enabledAppsCache[$appId]);
660
-		$this->getAppConfig()->setValue($appId, 'enabled', 'no');
661
-
662
-		// run uninstall steps
663
-		$appData = $this->getAppInfo($appId);
664
-		if (!is_null($appData)) {
665
-			\OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
666
-		}
667
-
668
-		$this->dispatcher->dispatchTyped(new AppDisableEvent($appId));
669
-		$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
670
-			ManagerEvent::EVENT_APP_DISABLE, $appId
671
-		));
672
-		$this->clearAppsCache();
673
-	}
674
-
675
-	/**
676
-	 * Get the directory for the given app.
677
-	 *
678
-	 * @throws AppPathNotFoundException if app folder can't be found
679
-	 */
680
-	public function getAppPath(string $appId): string {
681
-		$appPath = \OC_App::getAppPath($appId);
682
-		if ($appPath === false) {
683
-			throw new AppPathNotFoundException('Could not find path for ' . $appId);
684
-		}
685
-		return $appPath;
686
-	}
687
-
688
-	/**
689
-	 * Get the web path for the given app.
690
-	 *
691
-	 * @param string $appId
692
-	 * @return string
693
-	 * @throws AppPathNotFoundException if app path can't be found
694
-	 */
695
-	public function getAppWebPath(string $appId): string {
696
-		$appWebPath = \OC_App::getAppWebPath($appId);
697
-		if ($appWebPath === false) {
698
-			throw new AppPathNotFoundException('Could not find web path for ' . $appId);
699
-		}
700
-		return $appWebPath;
701
-	}
702
-
703
-	/**
704
-	 * Clear the cached list of apps when enabling/disabling an app
705
-	 */
706
-	public function clearAppsCache(): void {
707
-		$this->appInfos = [];
708
-	}
709
-
710
-	/**
711
-	 * Returns a list of apps that need upgrade
712
-	 *
713
-	 * @param string $version Nextcloud version as array of version components
714
-	 * @return array list of app info from apps that need an upgrade
715
-	 *
716
-	 * @internal
717
-	 */
718
-	public function getAppsNeedingUpgrade($version) {
719
-		$appsToUpgrade = [];
720
-		$apps = $this->getEnabledApps();
721
-		foreach ($apps as $appId) {
722
-			$appInfo = $this->getAppInfo($appId);
723
-			$appDbVersion = $this->getAppConfig()->getValue($appId, 'installed_version');
724
-			if ($appDbVersion
725
-				&& isset($appInfo['version'])
726
-				&& version_compare($appInfo['version'], $appDbVersion, '>')
727
-				&& \OC_App::isAppCompatible($version, $appInfo)
728
-			) {
729
-				$appsToUpgrade[] = $appInfo;
730
-			}
731
-		}
732
-
733
-		return $appsToUpgrade;
734
-	}
735
-
736
-	/**
737
-	 * Returns the app information from "appinfo/info.xml".
738
-	 *
739
-	 * @param string|null $lang
740
-	 * @return array|null app info
741
-	 */
742
-	public function getAppInfo(string $appId, bool $path = false, $lang = null) {
743
-		if ($path) {
744
-			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.');
745
-		}
746
-		if ($lang === null && isset($this->appInfos[$appId])) {
747
-			return $this->appInfos[$appId];
748
-		}
749
-		try {
750
-			$appPath = $this->getAppPath($appId);
751
-		} catch (AppPathNotFoundException) {
752
-			return null;
753
-		}
754
-		$file = $appPath . '/appinfo/info.xml';
755
-
756
-		$data = $this->getAppInfoByPath($file, $lang);
757
-
758
-		if ($lang === null) {
759
-			$this->appInfos[$appId] = $data;
760
-		}
761
-
762
-		return $data;
763
-	}
764
-
765
-	public function getAppInfoByPath(string $path, ?string $lang = null): ?array {
766
-		if (!str_ends_with($path, '/appinfo/info.xml')) {
767
-			return null;
768
-		}
769
-
770
-		$parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
771
-		$data = $parser->parse($path);
772
-
773
-		if (is_array($data)) {
774
-			$data = $parser->applyL10N($data, $lang);
775
-		}
776
-
777
-		return $data;
778
-	}
779
-
780
-	public function getAppVersion(string $appId, bool $useCache = true): string {
781
-		if (!$useCache || !isset($this->appVersions[$appId])) {
782
-			if ($appId === 'core') {
783
-				$this->appVersions[$appId] = $this->serverVersion->getVersionString();
784
-			} else {
785
-				$appInfo = $this->getAppInfo($appId);
786
-				$this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
787
-			}
788
-		}
789
-		return $this->appVersions[$appId];
790
-	}
791
-
792
-	/**
793
-	 * Returns the installed versions of all apps
794
-	 *
795
-	 * @return array<string, string>
796
-	 */
797
-	public function getAppInstalledVersions(bool $onlyEnabled = false): array {
798
-		return $this->getAppConfig()->getAppInstalledVersions($onlyEnabled);
799
-	}
800
-
801
-	/**
802
-	 * Returns a list of apps incompatible with the given version
803
-	 *
804
-	 * @param string $version Nextcloud version as array of version components
805
-	 *
806
-	 * @return array list of app info from incompatible apps
807
-	 *
808
-	 * @internal
809
-	 */
810
-	public function getIncompatibleApps(string $version): array {
811
-		$apps = $this->getEnabledApps();
812
-		$incompatibleApps = [];
813
-		foreach ($apps as $appId) {
814
-			$info = $this->getAppInfo($appId);
815
-			if ($info === null) {
816
-				$incompatibleApps[] = ['id' => $appId, 'name' => $appId];
817
-			} elseif (!\OC_App::isAppCompatible($version, $info)) {
818
-				$incompatibleApps[] = $info;
819
-			}
820
-		}
821
-		return $incompatibleApps;
822
-	}
823
-
824
-	/**
825
-	 * @inheritdoc
826
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
827
-	 */
828
-	public function isShipped($appId) {
829
-		$this->loadShippedJson();
830
-		return in_array($appId, $this->shippedApps, true);
831
-	}
832
-
833
-	private function isAlwaysEnabled(string $appId): bool {
834
-		if ($appId === 'core') {
835
-			return true;
836
-		}
837
-
838
-		$alwaysEnabled = $this->getAlwaysEnabledApps();
839
-		return in_array($appId, $alwaysEnabled, true);
840
-	}
841
-
842
-	/**
843
-	 * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
844
-	 * @throws \Exception
845
-	 */
846
-	private function loadShippedJson(): void {
847
-		if ($this->shippedApps === null) {
848
-			$shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
849
-			if (!file_exists($shippedJson)) {
850
-				throw new \Exception("File not found: $shippedJson");
851
-			}
852
-			$content = json_decode(file_get_contents($shippedJson), true);
853
-			$this->shippedApps = $content['shippedApps'];
854
-			$this->alwaysEnabled = $content['alwaysEnabled'];
855
-			$this->defaultEnabled = $content['defaultEnabled'];
856
-		}
857
-	}
858
-
859
-	/**
860
-	 * @inheritdoc
861
-	 */
862
-	public function getAlwaysEnabledApps() {
863
-		$this->loadShippedJson();
864
-		return $this->alwaysEnabled;
865
-	}
866
-
867
-	/**
868
-	 * @inheritdoc
869
-	 */
870
-	public function isDefaultEnabled(string $appId): bool {
871
-		return (in_array($appId, $this->getDefaultEnabledApps()));
872
-	}
873
-
874
-	/**
875
-	 * @inheritdoc
876
-	 */
877
-	public function getDefaultEnabledApps(): array {
878
-		$this->loadShippedJson();
879
-
880
-		return $this->defaultEnabled;
881
-	}
882
-
883
-	/**
884
-	 * @inheritdoc
885
-	 */
886
-	public function getDefaultAppForUser(?IUser $user = null, bool $withFallbacks = true): string {
887
-		$id = $this->getNavigationManager()->getDefaultEntryIdForUser($user, $withFallbacks);
888
-		$entry = $this->getNavigationManager()->get($id);
889
-		return (string)$entry['app'];
890
-	}
891
-
892
-	/**
893
-	 * @inheritdoc
894
-	 */
895
-	public function getDefaultApps(): array {
896
-		$ids = $this->getNavigationManager()->getDefaultEntryIds();
897
-
898
-		return array_values(array_unique(array_map(function (string $id) {
899
-			$entry = $this->getNavigationManager()->get($id);
900
-			return (string)$entry['app'];
901
-		}, $ids)));
902
-	}
903
-
904
-	/**
905
-	 * @inheritdoc
906
-	 */
907
-	public function setDefaultApps(array $defaultApps): void {
908
-		$entries = $this->getNavigationManager()->getAll();
909
-		$ids = [];
910
-		foreach ($defaultApps as $defaultApp) {
911
-			foreach ($entries as $entry) {
912
-				if ((string)$entry['app'] === $defaultApp) {
913
-					$ids[] = (string)$entry['id'];
914
-					break;
915
-				}
916
-			}
917
-		}
918
-		$this->getNavigationManager()->setDefaultEntryIds($ids);
919
-	}
920
-
921
-	public function isBackendRequired(string $backend): bool {
922
-		foreach ($this->appInfos as $appInfo) {
923
-			foreach ($appInfo['dependencies']['backend'] as $appBackend) {
924
-				if ($backend === $appBackend) {
925
-					return true;
926
-				}
927
-			}
928
-		}
929
-
930
-		return false;
931
-	}
932
-
933
-	/**
934
-	 * Clean the appId from forbidden characters
935
-	 *
936
-	 * @psalm-taint-escape callable
937
-	 * @psalm-taint-escape cookie
938
-	 * @psalm-taint-escape file
939
-	 * @psalm-taint-escape has_quotes
940
-	 * @psalm-taint-escape header
941
-	 * @psalm-taint-escape html
942
-	 * @psalm-taint-escape include
943
-	 * @psalm-taint-escape ldap
944
-	 * @psalm-taint-escape shell
945
-	 * @psalm-taint-escape sql
946
-	 * @psalm-taint-escape unserialize
947
-	 */
948
-	public function cleanAppId(string $app): string {
949
-		/* Only lowercase alphanumeric is allowed */
950
-		return preg_replace('/(^[0-9_]|[^a-z0-9_]+|_$)/', '', $app);
951
-	}
38
+    /**
39
+     * Apps with these types can not be enabled for certain groups only
40
+     * @var string[]
41
+     */
42
+    protected $protectedAppTypes = [
43
+        'filesystem',
44
+        'prelogin',
45
+        'authentication',
46
+        'logging',
47
+        'prevent_group_restriction',
48
+    ];
49
+
50
+    /** @var string[] $appId => $enabled */
51
+    private array $enabledAppsCache = [];
52
+
53
+    /** @var string[]|null */
54
+    private ?array $shippedApps = null;
55
+
56
+    private array $alwaysEnabled = [];
57
+    private array $defaultEnabled = [];
58
+
59
+    /** @var array */
60
+    private array $appInfos = [];
61
+
62
+    /** @var array */
63
+    private array $appVersions = [];
64
+
65
+    /** @var array */
66
+    private array $autoDisabledApps = [];
67
+    private array $appTypes = [];
68
+
69
+    /** @var array<string, true> */
70
+    private array $loadedApps = [];
71
+
72
+    private ?AppConfig $appConfig = null;
73
+    private ?IURLGenerator $urlGenerator = null;
74
+    private ?INavigationManager $navigationManager = null;
75
+
76
+    /**
77
+     * Be extremely careful when injecting classes here. The AppManager is used by the installer,
78
+     * so it needs to work before installation. See how AppConfig and IURLGenerator are injected for reference
79
+     */
80
+    public function __construct(
81
+        private IUserSession $userSession,
82
+        private IConfig $config,
83
+        private IGroupManager $groupManager,
84
+        private ICacheFactory $memCacheFactory,
85
+        private IEventDispatcher $dispatcher,
86
+        private LoggerInterface $logger,
87
+        private ServerVersion $serverVersion,
88
+        private ConfigManager $configManager,
89
+    ) {
90
+    }
91
+
92
+    private function getNavigationManager(): INavigationManager {
93
+        if ($this->navigationManager === null) {
94
+            $this->navigationManager = Server::get(INavigationManager::class);
95
+        }
96
+        return $this->navigationManager;
97
+    }
98
+
99
+    public function getAppIcon(string $appId, bool $dark = false): ?string {
100
+        $possibleIcons = $dark ? [$appId . '-dark.svg', 'app-dark.svg'] : [$appId . '.svg', 'app.svg'];
101
+        $icon = null;
102
+        foreach ($possibleIcons as $iconName) {
103
+            try {
104
+                $icon = $this->getUrlGenerator()->imagePath($appId, $iconName);
105
+                break;
106
+            } catch (\RuntimeException $e) {
107
+                // ignore
108
+            }
109
+        }
110
+        return $icon;
111
+    }
112
+
113
+    private function getAppConfig(): AppConfig {
114
+        if ($this->appConfig !== null) {
115
+            return $this->appConfig;
116
+        }
117
+        if (!$this->config->getSystemValueBool('installed', false)) {
118
+            throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
119
+        }
120
+        $this->appConfig = Server::get(AppConfig::class);
121
+        return $this->appConfig;
122
+    }
123
+
124
+    private function getUrlGenerator(): IURLGenerator {
125
+        if ($this->urlGenerator !== null) {
126
+            return $this->urlGenerator;
127
+        }
128
+        if (!$this->config->getSystemValueBool('installed', false)) {
129
+            throw new \Exception('Nextcloud is not installed yet, AppConfig is not available');
130
+        }
131
+        $this->urlGenerator = Server::get(IURLGenerator::class);
132
+        return $this->urlGenerator;
133
+    }
134
+
135
+    /**
136
+     * For all enabled apps, return the value of their 'enabled' config key.
137
+     *
138
+     * @return array<string,string> appId => enabled (may be 'yes', or a json encoded list of group ids)
139
+     */
140
+    private function getEnabledAppsValues(): array {
141
+        if (!$this->enabledAppsCache) {
142
+            /** @var array<string,string> */
143
+            $values = $this->getAppConfig()->searchValues('enabled', false, IAppConfig::VALUE_STRING);
144
+
145
+            $alwaysEnabledApps = $this->getAlwaysEnabledApps();
146
+            foreach ($alwaysEnabledApps as $appId) {
147
+                $values[$appId] = 'yes';
148
+            }
149
+
150
+            $this->enabledAppsCache = array_filter($values, function ($value) {
151
+                return $value !== 'no';
152
+            });
153
+            ksort($this->enabledAppsCache);
154
+        }
155
+        return $this->enabledAppsCache;
156
+    }
157
+
158
+    /**
159
+     * Deprecated alias
160
+     *
161
+     * @return string[]
162
+     */
163
+    public function getInstalledApps() {
164
+        return $this->getEnabledApps();
165
+    }
166
+
167
+    /**
168
+     * List all enabled apps, either for everyone or for some groups
169
+     *
170
+     * @return list<string>
171
+     */
172
+    public function getEnabledApps(): array {
173
+        return array_keys($this->getEnabledAppsValues());
174
+    }
175
+
176
+    /**
177
+     * Get a list of all apps in the apps folder
178
+     *
179
+     * @return list<string> an array of app names (string IDs)
180
+     */
181
+    public function getAllAppsInAppsFolders(): array {
182
+        $apps = [];
183
+
184
+        foreach (\OC::$APPSROOTS as $apps_dir) {
185
+            if (!is_readable($apps_dir['path'])) {
186
+                $this->logger->warning('unable to read app folder : ' . $apps_dir['path'], ['app' => 'core']);
187
+                continue;
188
+            }
189
+            $dh = opendir($apps_dir['path']);
190
+
191
+            if (is_resource($dh)) {
192
+                while (($file = readdir($dh)) !== false) {
193
+                    if (
194
+                        $file[0] != '.'
195
+                        && is_dir($apps_dir['path'] . '/' . $file)
196
+                        && is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')
197
+                    ) {
198
+                        $apps[] = $file;
199
+                    }
200
+                }
201
+            }
202
+        }
203
+
204
+        return array_values(array_unique($apps));
205
+    }
206
+
207
+    /**
208
+     * List all apps enabled for a user
209
+     *
210
+     * @param \OCP\IUser $user
211
+     * @return list<string>
212
+     */
213
+    public function getEnabledAppsForUser(IUser $user) {
214
+        $apps = $this->getEnabledAppsValues();
215
+        $appsForUser = array_filter($apps, function ($enabled) use ($user) {
216
+            return $this->checkAppForUser($enabled, $user);
217
+        });
218
+        return array_keys($appsForUser);
219
+    }
220
+
221
+    public function getEnabledAppsForGroup(IGroup $group): array {
222
+        $apps = $this->getEnabledAppsValues();
223
+        $appsForGroups = array_filter($apps, function ($enabled) use ($group) {
224
+            return $this->checkAppForGroups($enabled, $group);
225
+        });
226
+        return array_keys($appsForGroups);
227
+    }
228
+
229
+    /**
230
+     * Loads all apps
231
+     *
232
+     * @param string[] $types
233
+     * @return bool
234
+     *
235
+     * This function walks through the Nextcloud directory and loads all apps
236
+     * it can find. A directory contains an app if the file /appinfo/info.xml
237
+     * exists.
238
+     *
239
+     * if $types is set to non-empty array, only apps of those types will be loaded
240
+     */
241
+    public function loadApps(array $types = []): bool {
242
+        if ($this->config->getSystemValueBool('maintenance', false)) {
243
+            return false;
244
+        }
245
+        // Load the enabled apps here
246
+        $apps = \OC_App::getEnabledApps();
247
+
248
+        // Add each apps' folder as allowed class path
249
+        foreach ($apps as $app) {
250
+            // If the app is already loaded then autoloading it makes no sense
251
+            if (!$this->isAppLoaded($app)) {
252
+                $path = \OC_App::getAppPath($app);
253
+                if ($path !== false) {
254
+                    \OC_App::registerAutoloading($app, $path);
255
+                }
256
+            }
257
+        }
258
+
259
+        // prevent app loading from printing output
260
+        ob_start();
261
+        foreach ($apps as $app) {
262
+            if (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))) {
263
+                try {
264
+                    $this->loadApp($app);
265
+                } catch (\Throwable $e) {
266
+                    $this->logger->emergency('Error during app loading: ' . $e->getMessage(), [
267
+                        'exception' => $e,
268
+                        'app' => $app,
269
+                    ]);
270
+                }
271
+            }
272
+        }
273
+        ob_end_clean();
274
+
275
+        return true;
276
+    }
277
+
278
+    /**
279
+     * check if an app is of a specific type
280
+     *
281
+     * @param string $app
282
+     * @param array $types
283
+     * @return bool
284
+     */
285
+    public function isType(string $app, array $types): bool {
286
+        $appTypes = $this->getAppTypes($app);
287
+        foreach ($types as $type) {
288
+            if (in_array($type, $appTypes, true)) {
289
+                return true;
290
+            }
291
+        }
292
+        return false;
293
+    }
294
+
295
+    /**
296
+     * get the types of an app
297
+     *
298
+     * @param string $app
299
+     * @return string[]
300
+     */
301
+    private function getAppTypes(string $app): array {
302
+        //load the cache
303
+        if (count($this->appTypes) === 0) {
304
+            $this->appTypes = $this->getAppConfig()->getValues(false, 'types') ?: [];
305
+        }
306
+
307
+        if (isset($this->appTypes[$app])) {
308
+            return explode(',', $this->appTypes[$app]);
309
+        }
310
+
311
+        return [];
312
+    }
313
+
314
+    /**
315
+     * @return array
316
+     */
317
+    public function getAutoDisabledApps(): array {
318
+        return $this->autoDisabledApps;
319
+    }
320
+
321
+    public function getAppRestriction(string $appId): array {
322
+        $values = $this->getEnabledAppsValues();
323
+
324
+        if (!isset($values[$appId])) {
325
+            return [];
326
+        }
327
+
328
+        if ($values[$appId] === 'yes' || $values[$appId] === 'no') {
329
+            return [];
330
+        }
331
+        return json_decode($values[$appId], true);
332
+    }
333
+
334
+    /**
335
+     * Check if an app is enabled for user
336
+     *
337
+     * @param string $appId
338
+     * @param \OCP\IUser|null $user (optional) if not defined, the currently logged in user will be used
339
+     * @return bool
340
+     */
341
+    public function isEnabledForUser($appId, $user = null) {
342
+        if ($this->isAlwaysEnabled($appId)) {
343
+            return true;
344
+        }
345
+        if ($user === null) {
346
+            $user = $this->userSession->getUser();
347
+        }
348
+        $enabledAppsValues = $this->getEnabledAppsValues();
349
+        if (isset($enabledAppsValues[$appId])) {
350
+            return $this->checkAppForUser($enabledAppsValues[$appId], $user);
351
+        } else {
352
+            return false;
353
+        }
354
+    }
355
+
356
+    private function checkAppForUser(string $enabled, ?IUser $user): bool {
357
+        if ($enabled === 'yes') {
358
+            return true;
359
+        } elseif ($user === null) {
360
+            return false;
361
+        } else {
362
+            if (empty($enabled)) {
363
+                return false;
364
+            }
365
+
366
+            $groupIds = json_decode($enabled);
367
+
368
+            if (!is_array($groupIds)) {
369
+                $jsonError = json_last_error();
370
+                $jsonErrorMsg = json_last_error_msg();
371
+                // 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)
372
+                $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);
373
+                return false;
374
+            }
375
+
376
+            $userGroups = $this->groupManager->getUserGroupIds($user);
377
+            foreach ($userGroups as $groupId) {
378
+                if (in_array($groupId, $groupIds, true)) {
379
+                    return true;
380
+                }
381
+            }
382
+            return false;
383
+        }
384
+    }
385
+
386
+    private function checkAppForGroups(string $enabled, IGroup $group): bool {
387
+        if ($enabled === 'yes') {
388
+            return true;
389
+        } else {
390
+            if (empty($enabled)) {
391
+                return false;
392
+            }
393
+
394
+            $groupIds = json_decode($enabled);
395
+
396
+            if (!is_array($groupIds)) {
397
+                $jsonError = json_last_error();
398
+                $jsonErrorMsg = json_last_error_msg();
399
+                // 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)
400
+                $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);
401
+                return false;
402
+            }
403
+
404
+            return in_array($group->getGID(), $groupIds);
405
+        }
406
+    }
407
+
408
+    /**
409
+     * Check if an app is enabled in the instance
410
+     *
411
+     * Notice: This actually checks if the app is enabled and not only if it is installed.
412
+     *
413
+     * @param string $appId
414
+     */
415
+    public function isInstalled($appId): bool {
416
+        return $this->isEnabledForAnyone($appId);
417
+    }
418
+
419
+    public function isEnabledForAnyone(string $appId): bool {
420
+        $enabledAppsValues = $this->getEnabledAppsValues();
421
+        return isset($enabledAppsValues[$appId]);
422
+    }
423
+
424
+    /**
425
+     * Overwrite the `max-version` requirement for this app.
426
+     */
427
+    public function overwriteNextcloudRequirement(string $appId): void {
428
+        $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
429
+        if (!in_array($appId, $ignoreMaxApps, true)) {
430
+            $ignoreMaxApps[] = $appId;
431
+        }
432
+        $this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
433
+    }
434
+
435
+    /**
436
+     * Remove the `max-version` overwrite for this app.
437
+     * This means this app now again can not be enabled if the `max-version` is smaller than the current Nextcloud version.
438
+     */
439
+    public function removeOverwriteNextcloudRequirement(string $appId): void {
440
+        $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
441
+        $ignoreMaxApps = array_filter($ignoreMaxApps, fn (string $id) => $id !== $appId);
442
+        $this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
443
+    }
444
+
445
+    public function loadApp(string $app): void {
446
+        if (isset($this->loadedApps[$app])) {
447
+            return;
448
+        }
449
+        $this->loadedApps[$app] = true;
450
+        $appPath = \OC_App::getAppPath($app);
451
+        if ($appPath === false) {
452
+            return;
453
+        }
454
+        $eventLogger = \OC::$server->get(IEventLogger::class);
455
+        $eventLogger->start("bootstrap:load_app:$app", "Load app: $app");
456
+
457
+        // in case someone calls loadApp() directly
458
+        \OC_App::registerAutoloading($app, $appPath);
459
+
460
+        if (is_file($appPath . '/appinfo/app.php')) {
461
+            $this->logger->error('/appinfo/app.php is not supported anymore, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
462
+                'app' => $app,
463
+            ]);
464
+        }
465
+
466
+        $coordinator = Server::get(Coordinator::class);
467
+        $coordinator->bootApp($app);
468
+
469
+        $eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it");
470
+        $info = $this->getAppInfo($app);
471
+        if (!empty($info['activity'])) {
472
+            $activityManager = \OC::$server->get(IActivityManager::class);
473
+            if (!empty($info['activity']['filters'])) {
474
+                foreach ($info['activity']['filters'] as $filter) {
475
+                    $activityManager->registerFilter($filter);
476
+                }
477
+            }
478
+            if (!empty($info['activity']['settings'])) {
479
+                foreach ($info['activity']['settings'] as $setting) {
480
+                    $activityManager->registerSetting($setting);
481
+                }
482
+            }
483
+            if (!empty($info['activity']['providers'])) {
484
+                foreach ($info['activity']['providers'] as $provider) {
485
+                    $activityManager->registerProvider($provider);
486
+                }
487
+            }
488
+        }
489
+
490
+        if (!empty($info['settings'])) {
491
+            $settingsManager = \OC::$server->get(ISettingsManager::class);
492
+            if (!empty($info['settings']['admin'])) {
493
+                foreach ($info['settings']['admin'] as $setting) {
494
+                    $settingsManager->registerSetting('admin', $setting);
495
+                }
496
+            }
497
+            if (!empty($info['settings']['admin-section'])) {
498
+                foreach ($info['settings']['admin-section'] as $section) {
499
+                    $settingsManager->registerSection('admin', $section);
500
+                }
501
+            }
502
+            if (!empty($info['settings']['personal'])) {
503
+                foreach ($info['settings']['personal'] as $setting) {
504
+                    $settingsManager->registerSetting('personal', $setting);
505
+                }
506
+            }
507
+            if (!empty($info['settings']['personal-section'])) {
508
+                foreach ($info['settings']['personal-section'] as $section) {
509
+                    $settingsManager->registerSection('personal', $section);
510
+                }
511
+            }
512
+        }
513
+
514
+        if (!empty($info['collaboration']['plugins'])) {
515
+            // deal with one or many plugin entries
516
+            $plugins = isset($info['collaboration']['plugins']['plugin']['@value'])
517
+                ? [$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
518
+            $collaboratorSearch = null;
519
+            $autoCompleteManager = null;
520
+            foreach ($plugins as $plugin) {
521
+                if ($plugin['@attributes']['type'] === 'collaborator-search') {
522
+                    $pluginInfo = [
523
+                        'shareType' => $plugin['@attributes']['share-type'],
524
+                        'class' => $plugin['@value'],
525
+                    ];
526
+                    $collaboratorSearch ??= \OC::$server->get(ICollaboratorSearch::class);
527
+                    $collaboratorSearch->registerPlugin($pluginInfo);
528
+                } elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
529
+                    $autoCompleteManager ??= \OC::$server->get(IAutoCompleteManager::class);
530
+                    $autoCompleteManager->registerSorter($plugin['@value']);
531
+                }
532
+            }
533
+        }
534
+        $eventLogger->end("bootstrap:load_app:$app:info");
535
+
536
+        $eventLogger->end("bootstrap:load_app:$app");
537
+    }
538
+
539
+    /**
540
+     * Check if an app is loaded
541
+     * @param string $app app id
542
+     * @since 26.0.0
543
+     */
544
+    public function isAppLoaded(string $app): bool {
545
+        return isset($this->loadedApps[$app]);
546
+    }
547
+
548
+    /**
549
+     * Enable an app for every user
550
+     *
551
+     * @param string $appId
552
+     * @param bool $forceEnable
553
+     * @throws AppPathNotFoundException
554
+     * @throws \InvalidArgumentException if the application is not installed yet
555
+     */
556
+    public function enableApp(string $appId, bool $forceEnable = false): void {
557
+        // Check if app exists
558
+        $this->getAppPath($appId);
559
+
560
+        if ($this->config->getAppValue($appId, 'installed_version', '') === '') {
561
+            throw new \InvalidArgumentException("$appId is not installed, cannot be enabled.");
562
+        }
563
+
564
+        if ($forceEnable) {
565
+            $this->overwriteNextcloudRequirement($appId);
566
+        }
567
+
568
+        $this->enabledAppsCache[$appId] = 'yes';
569
+        $this->getAppConfig()->setValue($appId, 'enabled', 'yes');
570
+        $this->dispatcher->dispatchTyped(new AppEnableEvent($appId));
571
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
572
+            ManagerEvent::EVENT_APP_ENABLE, $appId
573
+        ));
574
+        $this->clearAppsCache();
575
+
576
+        $this->configManager->migrateConfigLexiconKeys($appId);
577
+    }
578
+
579
+    /**
580
+     * Whether a list of types contains a protected app type
581
+     *
582
+     * @param string[] $types
583
+     * @return bool
584
+     */
585
+    public function hasProtectedAppType($types) {
586
+        if (empty($types)) {
587
+            return false;
588
+        }
589
+
590
+        $protectedTypes = array_intersect($this->protectedAppTypes, $types);
591
+        return !empty($protectedTypes);
592
+    }
593
+
594
+    /**
595
+     * Enable an app only for specific groups
596
+     *
597
+     * @param string $appId
598
+     * @param IGroup[] $groups
599
+     * @param bool $forceEnable
600
+     * @throws \InvalidArgumentException if app can't be enabled for groups
601
+     * @throws AppPathNotFoundException
602
+     */
603
+    public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void {
604
+        // Check if app exists
605
+        $this->getAppPath($appId);
606
+
607
+        $info = $this->getAppInfo($appId);
608
+        if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) {
609
+            throw new \InvalidArgumentException("$appId can't be enabled for groups.");
610
+        }
611
+
612
+        if ($this->config->getAppValue($appId, 'installed_version', '') === '') {
613
+            throw new \InvalidArgumentException("$appId is not installed, cannot be enabled.");
614
+        }
615
+
616
+        if ($forceEnable) {
617
+            $this->overwriteNextcloudRequirement($appId);
618
+        }
619
+
620
+        /** @var string[] $groupIds */
621
+        $groupIds = array_map(function ($group) {
622
+            /** @var IGroup $group */
623
+            return ($group instanceof IGroup)
624
+                ? $group->getGID()
625
+                : $group;
626
+        }, $groups);
627
+
628
+        $this->enabledAppsCache[$appId] = json_encode($groupIds);
629
+        $this->getAppConfig()->setValue($appId, 'enabled', json_encode($groupIds));
630
+        $this->dispatcher->dispatchTyped(new AppEnableEvent($appId, $groupIds));
631
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
632
+            ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
633
+        ));
634
+        $this->clearAppsCache();
635
+
636
+        $this->configManager->migrateConfigLexiconKeys($appId);
637
+    }
638
+
639
+    /**
640
+     * Disable an app for every user
641
+     *
642
+     * @param string $appId
643
+     * @param bool $automaticDisabled
644
+     * @throws \Exception if app can't be disabled
645
+     */
646
+    public function disableApp($appId, $automaticDisabled = false): void {
647
+        if ($this->isAlwaysEnabled($appId)) {
648
+            throw new \Exception("$appId can't be disabled.");
649
+        }
650
+
651
+        if ($automaticDisabled) {
652
+            $previousSetting = $this->getAppConfig()->getValue($appId, 'enabled', 'yes');
653
+            if ($previousSetting !== 'yes' && $previousSetting !== 'no') {
654
+                $previousSetting = json_decode($previousSetting, true);
655
+            }
656
+            $this->autoDisabledApps[$appId] = $previousSetting;
657
+        }
658
+
659
+        unset($this->enabledAppsCache[$appId]);
660
+        $this->getAppConfig()->setValue($appId, 'enabled', 'no');
661
+
662
+        // run uninstall steps
663
+        $appData = $this->getAppInfo($appId);
664
+        if (!is_null($appData)) {
665
+            \OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
666
+        }
667
+
668
+        $this->dispatcher->dispatchTyped(new AppDisableEvent($appId));
669
+        $this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
670
+            ManagerEvent::EVENT_APP_DISABLE, $appId
671
+        ));
672
+        $this->clearAppsCache();
673
+    }
674
+
675
+    /**
676
+     * Get the directory for the given app.
677
+     *
678
+     * @throws AppPathNotFoundException if app folder can't be found
679
+     */
680
+    public function getAppPath(string $appId): string {
681
+        $appPath = \OC_App::getAppPath($appId);
682
+        if ($appPath === false) {
683
+            throw new AppPathNotFoundException('Could not find path for ' . $appId);
684
+        }
685
+        return $appPath;
686
+    }
687
+
688
+    /**
689
+     * Get the web path for the given app.
690
+     *
691
+     * @param string $appId
692
+     * @return string
693
+     * @throws AppPathNotFoundException if app path can't be found
694
+     */
695
+    public function getAppWebPath(string $appId): string {
696
+        $appWebPath = \OC_App::getAppWebPath($appId);
697
+        if ($appWebPath === false) {
698
+            throw new AppPathNotFoundException('Could not find web path for ' . $appId);
699
+        }
700
+        return $appWebPath;
701
+    }
702
+
703
+    /**
704
+     * Clear the cached list of apps when enabling/disabling an app
705
+     */
706
+    public function clearAppsCache(): void {
707
+        $this->appInfos = [];
708
+    }
709
+
710
+    /**
711
+     * Returns a list of apps that need upgrade
712
+     *
713
+     * @param string $version Nextcloud version as array of version components
714
+     * @return array list of app info from apps that need an upgrade
715
+     *
716
+     * @internal
717
+     */
718
+    public function getAppsNeedingUpgrade($version) {
719
+        $appsToUpgrade = [];
720
+        $apps = $this->getEnabledApps();
721
+        foreach ($apps as $appId) {
722
+            $appInfo = $this->getAppInfo($appId);
723
+            $appDbVersion = $this->getAppConfig()->getValue($appId, 'installed_version');
724
+            if ($appDbVersion
725
+                && isset($appInfo['version'])
726
+                && version_compare($appInfo['version'], $appDbVersion, '>')
727
+                && \OC_App::isAppCompatible($version, $appInfo)
728
+            ) {
729
+                $appsToUpgrade[] = $appInfo;
730
+            }
731
+        }
732
+
733
+        return $appsToUpgrade;
734
+    }
735
+
736
+    /**
737
+     * Returns the app information from "appinfo/info.xml".
738
+     *
739
+     * @param string|null $lang
740
+     * @return array|null app info
741
+     */
742
+    public function getAppInfo(string $appId, bool $path = false, $lang = null) {
743
+        if ($path) {
744
+            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.');
745
+        }
746
+        if ($lang === null && isset($this->appInfos[$appId])) {
747
+            return $this->appInfos[$appId];
748
+        }
749
+        try {
750
+            $appPath = $this->getAppPath($appId);
751
+        } catch (AppPathNotFoundException) {
752
+            return null;
753
+        }
754
+        $file = $appPath . '/appinfo/info.xml';
755
+
756
+        $data = $this->getAppInfoByPath($file, $lang);
757
+
758
+        if ($lang === null) {
759
+            $this->appInfos[$appId] = $data;
760
+        }
761
+
762
+        return $data;
763
+    }
764
+
765
+    public function getAppInfoByPath(string $path, ?string $lang = null): ?array {
766
+        if (!str_ends_with($path, '/appinfo/info.xml')) {
767
+            return null;
768
+        }
769
+
770
+        $parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
771
+        $data = $parser->parse($path);
772
+
773
+        if (is_array($data)) {
774
+            $data = $parser->applyL10N($data, $lang);
775
+        }
776
+
777
+        return $data;
778
+    }
779
+
780
+    public function getAppVersion(string $appId, bool $useCache = true): string {
781
+        if (!$useCache || !isset($this->appVersions[$appId])) {
782
+            if ($appId === 'core') {
783
+                $this->appVersions[$appId] = $this->serverVersion->getVersionString();
784
+            } else {
785
+                $appInfo = $this->getAppInfo($appId);
786
+                $this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
787
+            }
788
+        }
789
+        return $this->appVersions[$appId];
790
+    }
791
+
792
+    /**
793
+     * Returns the installed versions of all apps
794
+     *
795
+     * @return array<string, string>
796
+     */
797
+    public function getAppInstalledVersions(bool $onlyEnabled = false): array {
798
+        return $this->getAppConfig()->getAppInstalledVersions($onlyEnabled);
799
+    }
800
+
801
+    /**
802
+     * Returns a list of apps incompatible with the given version
803
+     *
804
+     * @param string $version Nextcloud version as array of version components
805
+     *
806
+     * @return array list of app info from incompatible apps
807
+     *
808
+     * @internal
809
+     */
810
+    public function getIncompatibleApps(string $version): array {
811
+        $apps = $this->getEnabledApps();
812
+        $incompatibleApps = [];
813
+        foreach ($apps as $appId) {
814
+            $info = $this->getAppInfo($appId);
815
+            if ($info === null) {
816
+                $incompatibleApps[] = ['id' => $appId, 'name' => $appId];
817
+            } elseif (!\OC_App::isAppCompatible($version, $info)) {
818
+                $incompatibleApps[] = $info;
819
+            }
820
+        }
821
+        return $incompatibleApps;
822
+    }
823
+
824
+    /**
825
+     * @inheritdoc
826
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
827
+     */
828
+    public function isShipped($appId) {
829
+        $this->loadShippedJson();
830
+        return in_array($appId, $this->shippedApps, true);
831
+    }
832
+
833
+    private function isAlwaysEnabled(string $appId): bool {
834
+        if ($appId === 'core') {
835
+            return true;
836
+        }
837
+
838
+        $alwaysEnabled = $this->getAlwaysEnabledApps();
839
+        return in_array($appId, $alwaysEnabled, true);
840
+    }
841
+
842
+    /**
843
+     * In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
844
+     * @throws \Exception
845
+     */
846
+    private function loadShippedJson(): void {
847
+        if ($this->shippedApps === null) {
848
+            $shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
849
+            if (!file_exists($shippedJson)) {
850
+                throw new \Exception("File not found: $shippedJson");
851
+            }
852
+            $content = json_decode(file_get_contents($shippedJson), true);
853
+            $this->shippedApps = $content['shippedApps'];
854
+            $this->alwaysEnabled = $content['alwaysEnabled'];
855
+            $this->defaultEnabled = $content['defaultEnabled'];
856
+        }
857
+    }
858
+
859
+    /**
860
+     * @inheritdoc
861
+     */
862
+    public function getAlwaysEnabledApps() {
863
+        $this->loadShippedJson();
864
+        return $this->alwaysEnabled;
865
+    }
866
+
867
+    /**
868
+     * @inheritdoc
869
+     */
870
+    public function isDefaultEnabled(string $appId): bool {
871
+        return (in_array($appId, $this->getDefaultEnabledApps()));
872
+    }
873
+
874
+    /**
875
+     * @inheritdoc
876
+     */
877
+    public function getDefaultEnabledApps(): array {
878
+        $this->loadShippedJson();
879
+
880
+        return $this->defaultEnabled;
881
+    }
882
+
883
+    /**
884
+     * @inheritdoc
885
+     */
886
+    public function getDefaultAppForUser(?IUser $user = null, bool $withFallbacks = true): string {
887
+        $id = $this->getNavigationManager()->getDefaultEntryIdForUser($user, $withFallbacks);
888
+        $entry = $this->getNavigationManager()->get($id);
889
+        return (string)$entry['app'];
890
+    }
891
+
892
+    /**
893
+     * @inheritdoc
894
+     */
895
+    public function getDefaultApps(): array {
896
+        $ids = $this->getNavigationManager()->getDefaultEntryIds();
897
+
898
+        return array_values(array_unique(array_map(function (string $id) {
899
+            $entry = $this->getNavigationManager()->get($id);
900
+            return (string)$entry['app'];
901
+        }, $ids)));
902
+    }
903
+
904
+    /**
905
+     * @inheritdoc
906
+     */
907
+    public function setDefaultApps(array $defaultApps): void {
908
+        $entries = $this->getNavigationManager()->getAll();
909
+        $ids = [];
910
+        foreach ($defaultApps as $defaultApp) {
911
+            foreach ($entries as $entry) {
912
+                if ((string)$entry['app'] === $defaultApp) {
913
+                    $ids[] = (string)$entry['id'];
914
+                    break;
915
+                }
916
+            }
917
+        }
918
+        $this->getNavigationManager()->setDefaultEntryIds($ids);
919
+    }
920
+
921
+    public function isBackendRequired(string $backend): bool {
922
+        foreach ($this->appInfos as $appInfo) {
923
+            foreach ($appInfo['dependencies']['backend'] as $appBackend) {
924
+                if ($backend === $appBackend) {
925
+                    return true;
926
+                }
927
+            }
928
+        }
929
+
930
+        return false;
931
+    }
932
+
933
+    /**
934
+     * Clean the appId from forbidden characters
935
+     *
936
+     * @psalm-taint-escape callable
937
+     * @psalm-taint-escape cookie
938
+     * @psalm-taint-escape file
939
+     * @psalm-taint-escape has_quotes
940
+     * @psalm-taint-escape header
941
+     * @psalm-taint-escape html
942
+     * @psalm-taint-escape include
943
+     * @psalm-taint-escape ldap
944
+     * @psalm-taint-escape shell
945
+     * @psalm-taint-escape sql
946
+     * @psalm-taint-escape unserialize
947
+     */
948
+    public function cleanAppId(string $app): string {
949
+        /* Only lowercase alphanumeric is allowed */
950
+        return preg_replace('/(^[0-9_]|[^a-z0-9_]+|_$)/', '', $app);
951
+    }
952 952
 }
Please login to merge, or discard this patch.
lib/private/App/DependencyAnalyzer.php 2 patches
Indentation   +301 added lines, -301 removed lines patch added patch discarded remove patch
@@ -13,305 +13,305 @@
 block discarded – undo
13 13
 use OCP\IL10N;
14 14
 
15 15
 class DependencyAnalyzer {
16
-	public function __construct(
17
-		private Platform $platform,
18
-		private IL10N $l,
19
-	) {
20
-	}
21
-
22
-	/**
23
-	 * @return array of missing dependencies
24
-	 */
25
-	public function analyze(array $app, bool $ignoreMax = false): array {
26
-		if (isset($app['dependencies'])) {
27
-			$dependencies = $app['dependencies'];
28
-		} else {
29
-			$dependencies = [];
30
-		}
31
-
32
-		return array_merge(
33
-			$this->analyzeArchitecture($dependencies),
34
-			$this->analyzePhpVersion($dependencies),
35
-			$this->analyzeDatabases($dependencies),
36
-			$this->analyzeCommands($dependencies),
37
-			$this->analyzeLibraries($dependencies),
38
-			$this->analyzeOS($dependencies),
39
-			$this->analyzeOC($dependencies, $app, $ignoreMax)
40
-		);
41
-	}
42
-
43
-	public function isMarkedCompatible(array $app): bool {
44
-		if (isset($app['dependencies'])) {
45
-			$dependencies = $app['dependencies'];
46
-		} else {
47
-			$dependencies = [];
48
-		}
49
-
50
-		$maxVersion = $this->getMaxVersion($dependencies, $app);
51
-		if ($maxVersion === null) {
52
-			return true;
53
-		}
54
-		return !$this->compareBigger($this->platform->getOcVersion(), $maxVersion);
55
-	}
56
-
57
-	/**
58
-	 * Truncates both versions to the lowest common version, e.g.
59
-	 * 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1,
60
-	 * 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1
61
-	 * @return string[] first element is the first version, second element is the
62
-	 *                  second version
63
-	 */
64
-	private function normalizeVersions(string $first, string $second): array {
65
-		$first = explode('.', $first);
66
-		$second = explode('.', $second);
67
-
68
-		// get both arrays to the same minimum size
69
-		$length = min(count($second), count($first));
70
-		$first = array_slice($first, 0, $length);
71
-		$second = array_slice($second, 0, $length);
72
-
73
-		return [implode('.', $first), implode('.', $second)];
74
-	}
75
-
76
-	/**
77
-	 * Parameters will be normalized and then passed into version_compare
78
-	 * in the same order they are specified in the method header
79
-	 * @return bool result similar to version_compare
80
-	 */
81
-	private function compare(string $first, string $second, string $operator): bool {
82
-		[$first, $second] = $this->normalizeVersions($first, $second);
83
-
84
-		return version_compare($first, $second, $operator);
85
-	}
86
-
87
-	/**
88
-	 * Checks if a version is bigger than another version
89
-	 * @return bool true if the first version is bigger than the second
90
-	 */
91
-	private function compareBigger(string $first, string $second): bool {
92
-		return $this->compare($first, $second, '>');
93
-	}
94
-
95
-	/**
96
-	 * Checks if a version is smaller than another version
97
-	 * @return bool true if the first version is smaller than the second
98
-	 */
99
-	private function compareSmaller(string $first, string $second): bool {
100
-		return $this->compare($first, $second, '<');
101
-	}
102
-
103
-	private function analyzePhpVersion(array $dependencies): array {
104
-		$missing = [];
105
-		if (isset($dependencies['php']['@attributes']['min-version'])) {
106
-			$minVersion = $dependencies['php']['@attributes']['min-version'];
107
-			if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) {
108
-				$missing[] = $this->l->t('PHP %s or higher is required.', [$minVersion]);
109
-			}
110
-		}
111
-		if (isset($dependencies['php']['@attributes']['max-version'])) {
112
-			$maxVersion = $dependencies['php']['@attributes']['max-version'];
113
-			if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) {
114
-				$missing[] = $this->l->t('PHP with a version lower than %s is required.', [$maxVersion]);
115
-			}
116
-		}
117
-		if (isset($dependencies['php']['@attributes']['min-int-size'])) {
118
-			$intSize = $dependencies['php']['@attributes']['min-int-size'];
119
-			if ($intSize > $this->platform->getIntSize() * 8) {
120
-				$missing[] = $this->l->t('%sbit or higher PHP required.', [$intSize]);
121
-			}
122
-		}
123
-		return $missing;
124
-	}
125
-
126
-	private function analyzeArchitecture(array $dependencies): array {
127
-		$missing = [];
128
-		if (!isset($dependencies['architecture'])) {
129
-			return $missing;
130
-		}
131
-
132
-		$supportedArchitectures = $dependencies['architecture'];
133
-		if (empty($supportedArchitectures)) {
134
-			return $missing;
135
-		}
136
-		if (!is_array($supportedArchitectures)) {
137
-			$supportedArchitectures = [$supportedArchitectures];
138
-		}
139
-		$supportedArchitectures = array_map(function ($architecture) {
140
-			return $this->getValue($architecture);
141
-		}, $supportedArchitectures);
142
-		$currentArchitecture = $this->platform->getArchitecture();
143
-		if (!in_array($currentArchitecture, $supportedArchitectures, true)) {
144
-			$missing[] = $this->l->t('The following architectures are supported: %s', [implode(', ', $supportedArchitectures)]);
145
-		}
146
-		return $missing;
147
-	}
148
-
149
-	private function analyzeDatabases(array $dependencies): array {
150
-		$missing = [];
151
-		if (!isset($dependencies['database'])) {
152
-			return $missing;
153
-		}
154
-
155
-		$supportedDatabases = $dependencies['database'];
156
-		if (empty($supportedDatabases)) {
157
-			return $missing;
158
-		}
159
-		if (!is_array($supportedDatabases)) {
160
-			$supportedDatabases = [$supportedDatabases];
161
-		}
162
-		if (isset($supportedDatabases['@value'])) {
163
-			$supportedDatabases = [$supportedDatabases];
164
-		}
165
-		$supportedDatabases = array_map(function ($db) {
166
-			return $this->getValue($db);
167
-		}, $supportedDatabases);
168
-		$currentDatabase = $this->platform->getDatabase();
169
-		if (!in_array($currentDatabase, $supportedDatabases)) {
170
-			$missing[] = $this->l->t('The following databases are supported: %s', [implode(', ', $supportedDatabases)]);
171
-		}
172
-		return $missing;
173
-	}
174
-
175
-	private function analyzeCommands(array $dependencies): array {
176
-		$missing = [];
177
-		if (!isset($dependencies['command'])) {
178
-			return $missing;
179
-		}
180
-
181
-		$commands = $dependencies['command'];
182
-		if (!is_array($commands)) {
183
-			$commands = [$commands];
184
-		}
185
-		if (isset($commands['@value'])) {
186
-			$commands = [$commands];
187
-		}
188
-		$os = $this->platform->getOS();
189
-		foreach ($commands as $command) {
190
-			if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) {
191
-				continue;
192
-			}
193
-			$commandName = $this->getValue($command);
194
-			if (!$this->platform->isCommandKnown($commandName)) {
195
-				$missing[] = $this->l->t('The command line tool %s could not be found', [$commandName]);
196
-			}
197
-		}
198
-		return $missing;
199
-	}
200
-
201
-	private function analyzeLibraries(array $dependencies): array {
202
-		$missing = [];
203
-		if (!isset($dependencies['lib'])) {
204
-			return $missing;
205
-		}
206
-
207
-		$libs = $dependencies['lib'];
208
-		if (!is_array($libs)) {
209
-			$libs = [$libs];
210
-		}
211
-		if (isset($libs['@value'])) {
212
-			$libs = [$libs];
213
-		}
214
-		foreach ($libs as $lib) {
215
-			$libName = $this->getValue($lib);
216
-			$libVersion = $this->platform->getLibraryVersion($libName);
217
-			if (is_null($libVersion)) {
218
-				$missing[] = $this->l->t('The library %s is not available.', [$libName]);
219
-				continue;
220
-			}
221
-
222
-			if (is_array($lib)) {
223
-				if (isset($lib['@attributes']['min-version'])) {
224
-					$minVersion = $lib['@attributes']['min-version'];
225
-					if ($this->compareSmaller($libVersion, $minVersion)) {
226
-						$missing[] = $this->l->t('Library %1$s with a version higher than %2$s is required - available version %3$s.',
227
-							[$libName, $minVersion, $libVersion]);
228
-					}
229
-				}
230
-				if (isset($lib['@attributes']['max-version'])) {
231
-					$maxVersion = $lib['@attributes']['max-version'];
232
-					if ($this->compareBigger($libVersion, $maxVersion)) {
233
-						$missing[] = $this->l->t('Library %1$s with a version lower than %2$s is required - available version %3$s.',
234
-							[$libName, $maxVersion, $libVersion]);
235
-					}
236
-				}
237
-			}
238
-		}
239
-		return $missing;
240
-	}
241
-
242
-	private function analyzeOS(array $dependencies): array {
243
-		$missing = [];
244
-		if (!isset($dependencies['os'])) {
245
-			return $missing;
246
-		}
247
-
248
-		$oss = $dependencies['os'];
249
-		if (empty($oss)) {
250
-			return $missing;
251
-		}
252
-		if (is_array($oss)) {
253
-			$oss = array_map(function ($os) {
254
-				return $this->getValue($os);
255
-			}, $oss);
256
-		} else {
257
-			$oss = [$oss];
258
-		}
259
-		$currentOS = $this->platform->getOS();
260
-		if (!in_array($currentOS, $oss)) {
261
-			$missing[] = $this->l->t('The following platforms are supported: %s', [implode(', ', $oss)]);
262
-		}
263
-		return $missing;
264
-	}
265
-
266
-	private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax): array {
267
-		$missing = [];
268
-		$minVersion = null;
269
-		if (isset($dependencies['nextcloud']['@attributes']['min-version'])) {
270
-			$minVersion = $dependencies['nextcloud']['@attributes']['min-version'];
271
-		} elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) {
272
-			$minVersion = $dependencies['owncloud']['@attributes']['min-version'];
273
-		} elseif (isset($appInfo['requiremin'])) {
274
-			$minVersion = $appInfo['requiremin'];
275
-		} elseif (isset($appInfo['require'])) {
276
-			$minVersion = $appInfo['require'];
277
-		}
278
-		$maxVersion = $this->getMaxVersion($dependencies, $appInfo);
279
-
280
-		if (!is_null($minVersion)) {
281
-			if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) {
282
-				$missing[] = $this->l->t('Server version %s or higher is required.', [$minVersion]);
283
-			}
284
-		}
285
-		if (!$ignoreMax && !is_null($maxVersion)) {
286
-			if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) {
287
-				$missing[] = $this->l->t('Server version %s or lower is required.', [$maxVersion]);
288
-			}
289
-		}
290
-		return $missing;
291
-	}
292
-
293
-	private function getMaxVersion(array $dependencies, array $appInfo): ?string {
294
-		if (isset($dependencies['nextcloud']['@attributes']['max-version'])) {
295
-			return $dependencies['nextcloud']['@attributes']['max-version'];
296
-		}
297
-		if (isset($dependencies['owncloud']['@attributes']['max-version'])) {
298
-			return $dependencies['owncloud']['@attributes']['max-version'];
299
-		}
300
-		if (isset($appInfo['requiremax'])) {
301
-			return $appInfo['requiremax'];
302
-		}
303
-
304
-		return null;
305
-	}
306
-
307
-	/**
308
-	 * @param mixed $element
309
-	 * @return mixed
310
-	 */
311
-	private function getValue($element) {
312
-		if (isset($element['@value'])) {
313
-			return $element['@value'];
314
-		}
315
-		return (string)$element;
316
-	}
16
+    public function __construct(
17
+        private Platform $platform,
18
+        private IL10N $l,
19
+    ) {
20
+    }
21
+
22
+    /**
23
+     * @return array of missing dependencies
24
+     */
25
+    public function analyze(array $app, bool $ignoreMax = false): array {
26
+        if (isset($app['dependencies'])) {
27
+            $dependencies = $app['dependencies'];
28
+        } else {
29
+            $dependencies = [];
30
+        }
31
+
32
+        return array_merge(
33
+            $this->analyzeArchitecture($dependencies),
34
+            $this->analyzePhpVersion($dependencies),
35
+            $this->analyzeDatabases($dependencies),
36
+            $this->analyzeCommands($dependencies),
37
+            $this->analyzeLibraries($dependencies),
38
+            $this->analyzeOS($dependencies),
39
+            $this->analyzeOC($dependencies, $app, $ignoreMax)
40
+        );
41
+    }
42
+
43
+    public function isMarkedCompatible(array $app): bool {
44
+        if (isset($app['dependencies'])) {
45
+            $dependencies = $app['dependencies'];
46
+        } else {
47
+            $dependencies = [];
48
+        }
49
+
50
+        $maxVersion = $this->getMaxVersion($dependencies, $app);
51
+        if ($maxVersion === null) {
52
+            return true;
53
+        }
54
+        return !$this->compareBigger($this->platform->getOcVersion(), $maxVersion);
55
+    }
56
+
57
+    /**
58
+     * Truncates both versions to the lowest common version, e.g.
59
+     * 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1,
60
+     * 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1
61
+     * @return string[] first element is the first version, second element is the
62
+     *                  second version
63
+     */
64
+    private function normalizeVersions(string $first, string $second): array {
65
+        $first = explode('.', $first);
66
+        $second = explode('.', $second);
67
+
68
+        // get both arrays to the same minimum size
69
+        $length = min(count($second), count($first));
70
+        $first = array_slice($first, 0, $length);
71
+        $second = array_slice($second, 0, $length);
72
+
73
+        return [implode('.', $first), implode('.', $second)];
74
+    }
75
+
76
+    /**
77
+     * Parameters will be normalized and then passed into version_compare
78
+     * in the same order they are specified in the method header
79
+     * @return bool result similar to version_compare
80
+     */
81
+    private function compare(string $first, string $second, string $operator): bool {
82
+        [$first, $second] = $this->normalizeVersions($first, $second);
83
+
84
+        return version_compare($first, $second, $operator);
85
+    }
86
+
87
+    /**
88
+     * Checks if a version is bigger than another version
89
+     * @return bool true if the first version is bigger than the second
90
+     */
91
+    private function compareBigger(string $first, string $second): bool {
92
+        return $this->compare($first, $second, '>');
93
+    }
94
+
95
+    /**
96
+     * Checks if a version is smaller than another version
97
+     * @return bool true if the first version is smaller than the second
98
+     */
99
+    private function compareSmaller(string $first, string $second): bool {
100
+        return $this->compare($first, $second, '<');
101
+    }
102
+
103
+    private function analyzePhpVersion(array $dependencies): array {
104
+        $missing = [];
105
+        if (isset($dependencies['php']['@attributes']['min-version'])) {
106
+            $minVersion = $dependencies['php']['@attributes']['min-version'];
107
+            if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) {
108
+                $missing[] = $this->l->t('PHP %s or higher is required.', [$minVersion]);
109
+            }
110
+        }
111
+        if (isset($dependencies['php']['@attributes']['max-version'])) {
112
+            $maxVersion = $dependencies['php']['@attributes']['max-version'];
113
+            if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) {
114
+                $missing[] = $this->l->t('PHP with a version lower than %s is required.', [$maxVersion]);
115
+            }
116
+        }
117
+        if (isset($dependencies['php']['@attributes']['min-int-size'])) {
118
+            $intSize = $dependencies['php']['@attributes']['min-int-size'];
119
+            if ($intSize > $this->platform->getIntSize() * 8) {
120
+                $missing[] = $this->l->t('%sbit or higher PHP required.', [$intSize]);
121
+            }
122
+        }
123
+        return $missing;
124
+    }
125
+
126
+    private function analyzeArchitecture(array $dependencies): array {
127
+        $missing = [];
128
+        if (!isset($dependencies['architecture'])) {
129
+            return $missing;
130
+        }
131
+
132
+        $supportedArchitectures = $dependencies['architecture'];
133
+        if (empty($supportedArchitectures)) {
134
+            return $missing;
135
+        }
136
+        if (!is_array($supportedArchitectures)) {
137
+            $supportedArchitectures = [$supportedArchitectures];
138
+        }
139
+        $supportedArchitectures = array_map(function ($architecture) {
140
+            return $this->getValue($architecture);
141
+        }, $supportedArchitectures);
142
+        $currentArchitecture = $this->platform->getArchitecture();
143
+        if (!in_array($currentArchitecture, $supportedArchitectures, true)) {
144
+            $missing[] = $this->l->t('The following architectures are supported: %s', [implode(', ', $supportedArchitectures)]);
145
+        }
146
+        return $missing;
147
+    }
148
+
149
+    private function analyzeDatabases(array $dependencies): array {
150
+        $missing = [];
151
+        if (!isset($dependencies['database'])) {
152
+            return $missing;
153
+        }
154
+
155
+        $supportedDatabases = $dependencies['database'];
156
+        if (empty($supportedDatabases)) {
157
+            return $missing;
158
+        }
159
+        if (!is_array($supportedDatabases)) {
160
+            $supportedDatabases = [$supportedDatabases];
161
+        }
162
+        if (isset($supportedDatabases['@value'])) {
163
+            $supportedDatabases = [$supportedDatabases];
164
+        }
165
+        $supportedDatabases = array_map(function ($db) {
166
+            return $this->getValue($db);
167
+        }, $supportedDatabases);
168
+        $currentDatabase = $this->platform->getDatabase();
169
+        if (!in_array($currentDatabase, $supportedDatabases)) {
170
+            $missing[] = $this->l->t('The following databases are supported: %s', [implode(', ', $supportedDatabases)]);
171
+        }
172
+        return $missing;
173
+    }
174
+
175
+    private function analyzeCommands(array $dependencies): array {
176
+        $missing = [];
177
+        if (!isset($dependencies['command'])) {
178
+            return $missing;
179
+        }
180
+
181
+        $commands = $dependencies['command'];
182
+        if (!is_array($commands)) {
183
+            $commands = [$commands];
184
+        }
185
+        if (isset($commands['@value'])) {
186
+            $commands = [$commands];
187
+        }
188
+        $os = $this->platform->getOS();
189
+        foreach ($commands as $command) {
190
+            if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) {
191
+                continue;
192
+            }
193
+            $commandName = $this->getValue($command);
194
+            if (!$this->platform->isCommandKnown($commandName)) {
195
+                $missing[] = $this->l->t('The command line tool %s could not be found', [$commandName]);
196
+            }
197
+        }
198
+        return $missing;
199
+    }
200
+
201
+    private function analyzeLibraries(array $dependencies): array {
202
+        $missing = [];
203
+        if (!isset($dependencies['lib'])) {
204
+            return $missing;
205
+        }
206
+
207
+        $libs = $dependencies['lib'];
208
+        if (!is_array($libs)) {
209
+            $libs = [$libs];
210
+        }
211
+        if (isset($libs['@value'])) {
212
+            $libs = [$libs];
213
+        }
214
+        foreach ($libs as $lib) {
215
+            $libName = $this->getValue($lib);
216
+            $libVersion = $this->platform->getLibraryVersion($libName);
217
+            if (is_null($libVersion)) {
218
+                $missing[] = $this->l->t('The library %s is not available.', [$libName]);
219
+                continue;
220
+            }
221
+
222
+            if (is_array($lib)) {
223
+                if (isset($lib['@attributes']['min-version'])) {
224
+                    $minVersion = $lib['@attributes']['min-version'];
225
+                    if ($this->compareSmaller($libVersion, $minVersion)) {
226
+                        $missing[] = $this->l->t('Library %1$s with a version higher than %2$s is required - available version %3$s.',
227
+                            [$libName, $minVersion, $libVersion]);
228
+                    }
229
+                }
230
+                if (isset($lib['@attributes']['max-version'])) {
231
+                    $maxVersion = $lib['@attributes']['max-version'];
232
+                    if ($this->compareBigger($libVersion, $maxVersion)) {
233
+                        $missing[] = $this->l->t('Library %1$s with a version lower than %2$s is required - available version %3$s.',
234
+                            [$libName, $maxVersion, $libVersion]);
235
+                    }
236
+                }
237
+            }
238
+        }
239
+        return $missing;
240
+    }
241
+
242
+    private function analyzeOS(array $dependencies): array {
243
+        $missing = [];
244
+        if (!isset($dependencies['os'])) {
245
+            return $missing;
246
+        }
247
+
248
+        $oss = $dependencies['os'];
249
+        if (empty($oss)) {
250
+            return $missing;
251
+        }
252
+        if (is_array($oss)) {
253
+            $oss = array_map(function ($os) {
254
+                return $this->getValue($os);
255
+            }, $oss);
256
+        } else {
257
+            $oss = [$oss];
258
+        }
259
+        $currentOS = $this->platform->getOS();
260
+        if (!in_array($currentOS, $oss)) {
261
+            $missing[] = $this->l->t('The following platforms are supported: %s', [implode(', ', $oss)]);
262
+        }
263
+        return $missing;
264
+    }
265
+
266
+    private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax): array {
267
+        $missing = [];
268
+        $minVersion = null;
269
+        if (isset($dependencies['nextcloud']['@attributes']['min-version'])) {
270
+            $minVersion = $dependencies['nextcloud']['@attributes']['min-version'];
271
+        } elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) {
272
+            $minVersion = $dependencies['owncloud']['@attributes']['min-version'];
273
+        } elseif (isset($appInfo['requiremin'])) {
274
+            $minVersion = $appInfo['requiremin'];
275
+        } elseif (isset($appInfo['require'])) {
276
+            $minVersion = $appInfo['require'];
277
+        }
278
+        $maxVersion = $this->getMaxVersion($dependencies, $appInfo);
279
+
280
+        if (!is_null($minVersion)) {
281
+            if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) {
282
+                $missing[] = $this->l->t('Server version %s or higher is required.', [$minVersion]);
283
+            }
284
+        }
285
+        if (!$ignoreMax && !is_null($maxVersion)) {
286
+            if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) {
287
+                $missing[] = $this->l->t('Server version %s or lower is required.', [$maxVersion]);
288
+            }
289
+        }
290
+        return $missing;
291
+    }
292
+
293
+    private function getMaxVersion(array $dependencies, array $appInfo): ?string {
294
+        if (isset($dependencies['nextcloud']['@attributes']['max-version'])) {
295
+            return $dependencies['nextcloud']['@attributes']['max-version'];
296
+        }
297
+        if (isset($dependencies['owncloud']['@attributes']['max-version'])) {
298
+            return $dependencies['owncloud']['@attributes']['max-version'];
299
+        }
300
+        if (isset($appInfo['requiremax'])) {
301
+            return $appInfo['requiremax'];
302
+        }
303
+
304
+        return null;
305
+    }
306
+
307
+    /**
308
+     * @param mixed $element
309
+     * @return mixed
310
+     */
311
+    private function getValue($element) {
312
+        if (isset($element['@value'])) {
313
+            return $element['@value'];
314
+        }
315
+        return (string)$element;
316
+    }
317 317
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -136,7 +136,7 @@  discard block
 block discarded – undo
136 136
 		if (!is_array($supportedArchitectures)) {
137 137
 			$supportedArchitectures = [$supportedArchitectures];
138 138
 		}
139
-		$supportedArchitectures = array_map(function ($architecture) {
139
+		$supportedArchitectures = array_map(function($architecture) {
140 140
 			return $this->getValue($architecture);
141 141
 		}, $supportedArchitectures);
142 142
 		$currentArchitecture = $this->platform->getArchitecture();
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
 		if (isset($supportedDatabases['@value'])) {
163 163
 			$supportedDatabases = [$supportedDatabases];
164 164
 		}
165
-		$supportedDatabases = array_map(function ($db) {
165
+		$supportedDatabases = array_map(function($db) {
166 166
 			return $this->getValue($db);
167 167
 		}, $supportedDatabases);
168 168
 		$currentDatabase = $this->platform->getDatabase();
@@ -250,7 +250,7 @@  discard block
 block discarded – undo
250 250
 			return $missing;
251 251
 		}
252 252
 		if (is_array($oss)) {
253
-			$oss = array_map(function ($os) {
253
+			$oss = array_map(function($os) {
254 254
 				return $this->getValue($os);
255 255
 			}, $oss);
256 256
 		} else {
@@ -312,6 +312,6 @@  discard block
 block discarded – undo
312 312
 		if (isset($element['@value'])) {
313 313
 			return $element['@value'];
314 314
 		}
315
-		return (string)$element;
315
+		return (string) $element;
316 316
 	}
317 317
 }
Please login to merge, or discard this patch.
lib/private/App/InfoParser.php 2 patches
Indentation   +309 added lines, -309 removed lines patch added patch discarded remove patch
@@ -11,338 +11,338 @@
 block discarded – undo
11 11
 use function simplexml_load_string;
12 12
 
13 13
 class InfoParser {
14
-	/**
15
-	 * @param ICache|null $cache
16
-	 */
17
-	public function __construct(
18
-		private ?ICache $cache = null,
19
-	) {
20
-	}
14
+    /**
15
+     * @param ICache|null $cache
16
+     */
17
+    public function __construct(
18
+        private ?ICache $cache = null,
19
+    ) {
20
+    }
21 21
 
22
-	/**
23
-	 * @param string $file the xml file to be loaded
24
-	 * @return null|array where null is an indicator for an error
25
-	 */
26
-	public function parse(string $file): ?array {
27
-		if (!file_exists($file)) {
28
-			return null;
29
-		}
22
+    /**
23
+     * @param string $file the xml file to be loaded
24
+     * @return null|array where null is an indicator for an error
25
+     */
26
+    public function parse(string $file): ?array {
27
+        if (!file_exists($file)) {
28
+            return null;
29
+        }
30 30
 
31
-		if ($this->cache !== null) {
32
-			$fileCacheKey = $file . filemtime($file);
33
-			if ($cachedValue = $this->cache->get($fileCacheKey)) {
34
-				return json_decode($cachedValue, true);
35
-			}
36
-		}
31
+        if ($this->cache !== null) {
32
+            $fileCacheKey = $file . filemtime($file);
33
+            if ($cachedValue = $this->cache->get($fileCacheKey)) {
34
+                return json_decode($cachedValue, true);
35
+            }
36
+        }
37 37
 
38
-		libxml_use_internal_errors(true);
39
-		$xml = simplexml_load_string(file_get_contents($file));
38
+        libxml_use_internal_errors(true);
39
+        $xml = simplexml_load_string(file_get_contents($file));
40 40
 
41
-		if ($xml === false) {
42
-			libxml_clear_errors();
43
-			return null;
44
-		}
45
-		$array = $this->xmlToArray($xml);
41
+        if ($xml === false) {
42
+            libxml_clear_errors();
43
+            return null;
44
+        }
45
+        $array = $this->xmlToArray($xml);
46 46
 
47
-		if (is_string($array)) {
48
-			return null;
49
-		}
47
+        if (is_string($array)) {
48
+            return null;
49
+        }
50 50
 
51
-		if (!array_key_exists('info', $array)) {
52
-			$array['info'] = [];
53
-		}
54
-		if (!array_key_exists('remote', $array)) {
55
-			$array['remote'] = [];
56
-		}
57
-		if (!array_key_exists('public', $array)) {
58
-			$array['public'] = [];
59
-		}
60
-		if (!array_key_exists('types', $array)) {
61
-			$array['types'] = [];
62
-		}
63
-		if (!array_key_exists('repair-steps', $array)) {
64
-			$array['repair-steps'] = [];
65
-		}
66
-		if (!array_key_exists('install', $array['repair-steps'])) {
67
-			$array['repair-steps']['install'] = [];
68
-		}
69
-		if (!array_key_exists('pre-migration', $array['repair-steps'])) {
70
-			$array['repair-steps']['pre-migration'] = [];
71
-		}
72
-		if (!array_key_exists('post-migration', $array['repair-steps'])) {
73
-			$array['repair-steps']['post-migration'] = [];
74
-		}
75
-		if (!array_key_exists('live-migration', $array['repair-steps'])) {
76
-			$array['repair-steps']['live-migration'] = [];
77
-		}
78
-		if (!array_key_exists('uninstall', $array['repair-steps'])) {
79
-			$array['repair-steps']['uninstall'] = [];
80
-		}
81
-		if (!array_key_exists('background-jobs', $array)) {
82
-			$array['background-jobs'] = [];
83
-		}
84
-		if (!array_key_exists('two-factor-providers', $array)) {
85
-			$array['two-factor-providers'] = [];
86
-		}
87
-		if (!array_key_exists('commands', $array)) {
88
-			$array['commands'] = [];
89
-		}
90
-		if (!array_key_exists('activity', $array)) {
91
-			$array['activity'] = [];
92
-		}
93
-		if (!array_key_exists('filters', $array['activity'])) {
94
-			$array['activity']['filters'] = [];
95
-		}
96
-		if (!array_key_exists('settings', $array['activity'])) {
97
-			$array['activity']['settings'] = [];
98
-		}
99
-		if (!array_key_exists('providers', $array['activity'])) {
100
-			$array['activity']['providers'] = [];
101
-		}
102
-		if (!array_key_exists('settings', $array)) {
103
-			$array['settings'] = [];
104
-		}
105
-		if (!array_key_exists('admin', $array['settings'])) {
106
-			$array['settings']['admin'] = [];
107
-		}
108
-		if (!array_key_exists('admin-section', $array['settings'])) {
109
-			$array['settings']['admin-section'] = [];
110
-		}
111
-		if (!array_key_exists('personal', $array['settings'])) {
112
-			$array['settings']['personal'] = [];
113
-		}
114
-		if (!array_key_exists('personal-section', $array['settings'])) {
115
-			$array['settings']['personal-section'] = [];
116
-		}
117
-		if (!array_key_exists('dependencies', $array)) {
118
-			$array['dependencies'] = [];
119
-		}
120
-		if (!array_key_exists('backend', $array['dependencies'])) {
121
-			$array['dependencies']['backend'] = [];
122
-		}
51
+        if (!array_key_exists('info', $array)) {
52
+            $array['info'] = [];
53
+        }
54
+        if (!array_key_exists('remote', $array)) {
55
+            $array['remote'] = [];
56
+        }
57
+        if (!array_key_exists('public', $array)) {
58
+            $array['public'] = [];
59
+        }
60
+        if (!array_key_exists('types', $array)) {
61
+            $array['types'] = [];
62
+        }
63
+        if (!array_key_exists('repair-steps', $array)) {
64
+            $array['repair-steps'] = [];
65
+        }
66
+        if (!array_key_exists('install', $array['repair-steps'])) {
67
+            $array['repair-steps']['install'] = [];
68
+        }
69
+        if (!array_key_exists('pre-migration', $array['repair-steps'])) {
70
+            $array['repair-steps']['pre-migration'] = [];
71
+        }
72
+        if (!array_key_exists('post-migration', $array['repair-steps'])) {
73
+            $array['repair-steps']['post-migration'] = [];
74
+        }
75
+        if (!array_key_exists('live-migration', $array['repair-steps'])) {
76
+            $array['repair-steps']['live-migration'] = [];
77
+        }
78
+        if (!array_key_exists('uninstall', $array['repair-steps'])) {
79
+            $array['repair-steps']['uninstall'] = [];
80
+        }
81
+        if (!array_key_exists('background-jobs', $array)) {
82
+            $array['background-jobs'] = [];
83
+        }
84
+        if (!array_key_exists('two-factor-providers', $array)) {
85
+            $array['two-factor-providers'] = [];
86
+        }
87
+        if (!array_key_exists('commands', $array)) {
88
+            $array['commands'] = [];
89
+        }
90
+        if (!array_key_exists('activity', $array)) {
91
+            $array['activity'] = [];
92
+        }
93
+        if (!array_key_exists('filters', $array['activity'])) {
94
+            $array['activity']['filters'] = [];
95
+        }
96
+        if (!array_key_exists('settings', $array['activity'])) {
97
+            $array['activity']['settings'] = [];
98
+        }
99
+        if (!array_key_exists('providers', $array['activity'])) {
100
+            $array['activity']['providers'] = [];
101
+        }
102
+        if (!array_key_exists('settings', $array)) {
103
+            $array['settings'] = [];
104
+        }
105
+        if (!array_key_exists('admin', $array['settings'])) {
106
+            $array['settings']['admin'] = [];
107
+        }
108
+        if (!array_key_exists('admin-section', $array['settings'])) {
109
+            $array['settings']['admin-section'] = [];
110
+        }
111
+        if (!array_key_exists('personal', $array['settings'])) {
112
+            $array['settings']['personal'] = [];
113
+        }
114
+        if (!array_key_exists('personal-section', $array['settings'])) {
115
+            $array['settings']['personal-section'] = [];
116
+        }
117
+        if (!array_key_exists('dependencies', $array)) {
118
+            $array['dependencies'] = [];
119
+        }
120
+        if (!array_key_exists('backend', $array['dependencies'])) {
121
+            $array['dependencies']['backend'] = [];
122
+        }
123 123
 
124
-		if (array_key_exists('types', $array)) {
125
-			if (is_array($array['types'])) {
126
-				foreach ($array['types'] as $type => $v) {
127
-					unset($array['types'][$type]);
128
-					if (is_string($type)) {
129
-						$array['types'][] = $type;
130
-					}
131
-				}
132
-			} else {
133
-				$array['types'] = [];
134
-			}
135
-		}
136
-		if (isset($array['repair-steps']['install']['step']) && is_array($array['repair-steps']['install']['step'])) {
137
-			$array['repair-steps']['install'] = $array['repair-steps']['install']['step'];
138
-		}
139
-		if (isset($array['repair-steps']['pre-migration']['step']) && is_array($array['repair-steps']['pre-migration']['step'])) {
140
-			$array['repair-steps']['pre-migration'] = $array['repair-steps']['pre-migration']['step'];
141
-		}
142
-		if (isset($array['repair-steps']['post-migration']['step']) && is_array($array['repair-steps']['post-migration']['step'])) {
143
-			$array['repair-steps']['post-migration'] = $array['repair-steps']['post-migration']['step'];
144
-		}
145
-		if (isset($array['repair-steps']['live-migration']['step']) && is_array($array['repair-steps']['live-migration']['step'])) {
146
-			$array['repair-steps']['live-migration'] = $array['repair-steps']['live-migration']['step'];
147
-		}
148
-		if (isset($array['repair-steps']['uninstall']['step']) && is_array($array['repair-steps']['uninstall']['step'])) {
149
-			$array['repair-steps']['uninstall'] = $array['repair-steps']['uninstall']['step'];
150
-		}
151
-		if (isset($array['background-jobs']['job']) && is_array($array['background-jobs']['job'])) {
152
-			$array['background-jobs'] = $array['background-jobs']['job'];
153
-		}
154
-		if (isset($array['commands']['command']) && is_array($array['commands']['command'])) {
155
-			$array['commands'] = $array['commands']['command'];
156
-		}
157
-		if (isset($array['two-factor-providers']['provider']) && is_array($array['two-factor-providers']['provider'])) {
158
-			$array['two-factor-providers'] = $array['two-factor-providers']['provider'];
159
-		}
160
-		if (isset($array['activity']['filters']['filter']) && is_array($array['activity']['filters']['filter'])) {
161
-			$array['activity']['filters'] = $array['activity']['filters']['filter'];
162
-		}
163
-		if (isset($array['activity']['settings']['setting']) && is_array($array['activity']['settings']['setting'])) {
164
-			$array['activity']['settings'] = $array['activity']['settings']['setting'];
165
-		}
166
-		if (isset($array['activity']['providers']['provider']) && is_array($array['activity']['providers']['provider'])) {
167
-			$array['activity']['providers'] = $array['activity']['providers']['provider'];
168
-		}
169
-		if (isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin'])
170
-			&& is_array($array['collaboration']['collaborators']['searchPlugins']['searchPlugin'])
171
-			&& !isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']['class'])
172
-		) {
173
-			$array['collaboration']['collaborators']['searchPlugins'] = $array['collaboration']['collaborators']['searchPlugins']['searchPlugin'];
174
-		}
175
-		if (isset($array['settings']['admin']) && !is_array($array['settings']['admin'])) {
176
-			$array['settings']['admin'] = [$array['settings']['admin']];
177
-		}
178
-		if (isset($array['settings']['admin-section']) && !is_array($array['settings']['admin-section'])) {
179
-			$array['settings']['admin-section'] = [$array['settings']['admin-section']];
180
-		}
181
-		if (isset($array['settings']['personal']) && !is_array($array['settings']['personal'])) {
182
-			$array['settings']['personal'] = [$array['settings']['personal']];
183
-		}
184
-		if (isset($array['settings']['personal-section']) && !is_array($array['settings']['personal-section'])) {
185
-			$array['settings']['personal-section'] = [$array['settings']['personal-section']];
186
-		}
187
-		if (isset($array['navigations']['navigation']) && $this->isNavigationItem($array['navigations']['navigation'])) {
188
-			$array['navigations']['navigation'] = [$array['navigations']['navigation']];
189
-		}
190
-		if (isset($array['dependencies']['backend']) && !is_array($array['dependencies']['backend'])) {
191
-			$array['dependencies']['backend'] = [$array['dependencies']['backend']];
192
-		}
124
+        if (array_key_exists('types', $array)) {
125
+            if (is_array($array['types'])) {
126
+                foreach ($array['types'] as $type => $v) {
127
+                    unset($array['types'][$type]);
128
+                    if (is_string($type)) {
129
+                        $array['types'][] = $type;
130
+                    }
131
+                }
132
+            } else {
133
+                $array['types'] = [];
134
+            }
135
+        }
136
+        if (isset($array['repair-steps']['install']['step']) && is_array($array['repair-steps']['install']['step'])) {
137
+            $array['repair-steps']['install'] = $array['repair-steps']['install']['step'];
138
+        }
139
+        if (isset($array['repair-steps']['pre-migration']['step']) && is_array($array['repair-steps']['pre-migration']['step'])) {
140
+            $array['repair-steps']['pre-migration'] = $array['repair-steps']['pre-migration']['step'];
141
+        }
142
+        if (isset($array['repair-steps']['post-migration']['step']) && is_array($array['repair-steps']['post-migration']['step'])) {
143
+            $array['repair-steps']['post-migration'] = $array['repair-steps']['post-migration']['step'];
144
+        }
145
+        if (isset($array['repair-steps']['live-migration']['step']) && is_array($array['repair-steps']['live-migration']['step'])) {
146
+            $array['repair-steps']['live-migration'] = $array['repair-steps']['live-migration']['step'];
147
+        }
148
+        if (isset($array['repair-steps']['uninstall']['step']) && is_array($array['repair-steps']['uninstall']['step'])) {
149
+            $array['repair-steps']['uninstall'] = $array['repair-steps']['uninstall']['step'];
150
+        }
151
+        if (isset($array['background-jobs']['job']) && is_array($array['background-jobs']['job'])) {
152
+            $array['background-jobs'] = $array['background-jobs']['job'];
153
+        }
154
+        if (isset($array['commands']['command']) && is_array($array['commands']['command'])) {
155
+            $array['commands'] = $array['commands']['command'];
156
+        }
157
+        if (isset($array['two-factor-providers']['provider']) && is_array($array['two-factor-providers']['provider'])) {
158
+            $array['two-factor-providers'] = $array['two-factor-providers']['provider'];
159
+        }
160
+        if (isset($array['activity']['filters']['filter']) && is_array($array['activity']['filters']['filter'])) {
161
+            $array['activity']['filters'] = $array['activity']['filters']['filter'];
162
+        }
163
+        if (isset($array['activity']['settings']['setting']) && is_array($array['activity']['settings']['setting'])) {
164
+            $array['activity']['settings'] = $array['activity']['settings']['setting'];
165
+        }
166
+        if (isset($array['activity']['providers']['provider']) && is_array($array['activity']['providers']['provider'])) {
167
+            $array['activity']['providers'] = $array['activity']['providers']['provider'];
168
+        }
169
+        if (isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin'])
170
+            && is_array($array['collaboration']['collaborators']['searchPlugins']['searchPlugin'])
171
+            && !isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']['class'])
172
+        ) {
173
+            $array['collaboration']['collaborators']['searchPlugins'] = $array['collaboration']['collaborators']['searchPlugins']['searchPlugin'];
174
+        }
175
+        if (isset($array['settings']['admin']) && !is_array($array['settings']['admin'])) {
176
+            $array['settings']['admin'] = [$array['settings']['admin']];
177
+        }
178
+        if (isset($array['settings']['admin-section']) && !is_array($array['settings']['admin-section'])) {
179
+            $array['settings']['admin-section'] = [$array['settings']['admin-section']];
180
+        }
181
+        if (isset($array['settings']['personal']) && !is_array($array['settings']['personal'])) {
182
+            $array['settings']['personal'] = [$array['settings']['personal']];
183
+        }
184
+        if (isset($array['settings']['personal-section']) && !is_array($array['settings']['personal-section'])) {
185
+            $array['settings']['personal-section'] = [$array['settings']['personal-section']];
186
+        }
187
+        if (isset($array['navigations']['navigation']) && $this->isNavigationItem($array['navigations']['navigation'])) {
188
+            $array['navigations']['navigation'] = [$array['navigations']['navigation']];
189
+        }
190
+        if (isset($array['dependencies']['backend']) && !is_array($array['dependencies']['backend'])) {
191
+            $array['dependencies']['backend'] = [$array['dependencies']['backend']];
192
+        }
193 193
 
194
-		// Ensure some fields are always arrays
195
-		if (isset($array['screenshot']) && !is_array($array['screenshot'])) {
196
-			$array['screenshot'] = [$array['screenshot']];
197
-		}
198
-		if (isset($array['author']) && !is_array($array['author'])) {
199
-			$array['author'] = [$array['author']];
200
-		}
201
-		if (isset($array['category']) && !is_array($array['category'])) {
202
-			$array['category'] = [$array['category']];
203
-		}
194
+        // Ensure some fields are always arrays
195
+        if (isset($array['screenshot']) && !is_array($array['screenshot'])) {
196
+            $array['screenshot'] = [$array['screenshot']];
197
+        }
198
+        if (isset($array['author']) && !is_array($array['author'])) {
199
+            $array['author'] = [$array['author']];
200
+        }
201
+        if (isset($array['category']) && !is_array($array['category'])) {
202
+            $array['category'] = [$array['category']];
203
+        }
204 204
 
205
-		if ($this->cache !== null) {
206
-			$this->cache->set($fileCacheKey, json_encode($array));
207
-		}
208
-		return $array;
209
-	}
205
+        if ($this->cache !== null) {
206
+            $this->cache->set($fileCacheKey, json_encode($array));
207
+        }
208
+        return $array;
209
+    }
210 210
 
211
-	private function isNavigationItem(array $data): bool {
212
-		// Allow settings navigation items with no route entry
213
-		$type = $data['type'] ?? 'link';
214
-		if ($type === 'settings') {
215
-			return isset($data['name']);
216
-		}
217
-		return isset($data['name'], $data['route']);
218
-	}
211
+    private function isNavigationItem(array $data): bool {
212
+        // Allow settings navigation items with no route entry
213
+        $type = $data['type'] ?? 'link';
214
+        if ($type === 'settings') {
215
+            return isset($data['name']);
216
+        }
217
+        return isset($data['name'], $data['route']);
218
+    }
219 219
 
220
-	public function xmlToArray(\SimpleXMLElement $xml): array|string {
221
-		$children = $xml->children();
222
-		if ($children === null || count($children) === 0) {
223
-			return (string)$xml;
224
-		}
220
+    public function xmlToArray(\SimpleXMLElement $xml): array|string {
221
+        $children = $xml->children();
222
+        if ($children === null || count($children) === 0) {
223
+            return (string)$xml;
224
+        }
225 225
 
226
-		$array = [];
227
-		foreach ($children as $element => $node) {
228
-			if ($element === null) {
229
-				throw new \InvalidArgumentException('xml contains a null element');
230
-			}
231
-			$totalElement = count($xml->{$element});
226
+        $array = [];
227
+        foreach ($children as $element => $node) {
228
+            if ($element === null) {
229
+                throw new \InvalidArgumentException('xml contains a null element');
230
+            }
231
+            $totalElement = count($xml->{$element});
232 232
 
233
-			if (!isset($array[$element])) {
234
-				$array[$element] = $totalElement > 1 ? [] : '';
235
-			}
236
-			/** @var \SimpleXMLElement $node */
237
-			// Has attributes
238
-			if ($attributes = $node->attributes()) {
239
-				$data = [
240
-					'@attributes' => [],
241
-				];
242
-				$converted = $this->xmlToArray($node);
243
-				if (is_string($converted)) {
244
-					if (!empty($converted)) {
245
-						$data['@value'] = $converted;
246
-					}
247
-				} else {
248
-					$data = array_merge($data, $converted);
249
-				}
250
-				foreach ($attributes as $attr => $value) {
251
-					if ($attr === null) {
252
-						throw new \InvalidArgumentException('xml contains a null element');
253
-					}
254
-					$data['@attributes'][$attr] = (string)$value;
255
-				}
233
+            if (!isset($array[$element])) {
234
+                $array[$element] = $totalElement > 1 ? [] : '';
235
+            }
236
+            /** @var \SimpleXMLElement $node */
237
+            // Has attributes
238
+            if ($attributes = $node->attributes()) {
239
+                $data = [
240
+                    '@attributes' => [],
241
+                ];
242
+                $converted = $this->xmlToArray($node);
243
+                if (is_string($converted)) {
244
+                    if (!empty($converted)) {
245
+                        $data['@value'] = $converted;
246
+                    }
247
+                } else {
248
+                    $data = array_merge($data, $converted);
249
+                }
250
+                foreach ($attributes as $attr => $value) {
251
+                    if ($attr === null) {
252
+                        throw new \InvalidArgumentException('xml contains a null element');
253
+                    }
254
+                    $data['@attributes'][$attr] = (string)$value;
255
+                }
256 256
 
257
-				if ($totalElement > 1) {
258
-					$array[$element][] = $data;
259
-				} else {
260
-					$array[$element] = $data;
261
-				}
262
-				// Just a value
263
-			} else {
264
-				if ($totalElement > 1) {
265
-					$array[$element][] = $this->xmlToArray($node);
266
-				} else {
267
-					$array[$element] = $this->xmlToArray($node);
268
-				}
269
-			}
270
-		}
257
+                if ($totalElement > 1) {
258
+                    $array[$element][] = $data;
259
+                } else {
260
+                    $array[$element] = $data;
261
+                }
262
+                // Just a value
263
+            } else {
264
+                if ($totalElement > 1) {
265
+                    $array[$element][] = $this->xmlToArray($node);
266
+                } else {
267
+                    $array[$element] = $this->xmlToArray($node);
268
+                }
269
+            }
270
+        }
271 271
 
272
-		return $array;
273
-	}
272
+        return $array;
273
+    }
274 274
 
275
-	/**
276
-	 * Select the appropriate l10n version for fields name, summary and description
277
-	 */
278
-	public function applyL10N(array $data, ?string $lang = null): array {
279
-		if ($lang !== '' && $lang !== null) {
280
-			if (isset($data['name']) && is_array($data['name'])) {
281
-				$data['name'] = $this->findBestL10NOption($data['name'], $lang);
282
-			}
283
-			if (isset($data['summary']) && is_array($data['summary'])) {
284
-				$data['summary'] = $this->findBestL10NOption($data['summary'], $lang);
285
-			}
286
-			if (isset($data['description']) && is_array($data['description'])) {
287
-				$data['description'] = trim($this->findBestL10NOption($data['description'], $lang));
288
-			}
289
-		} elseif (isset($data['description']) && is_string($data['description'])) {
290
-			$data['description'] = trim($data['description']);
291
-		} else {
292
-			$data['description'] = '';
293
-		}
275
+    /**
276
+     * Select the appropriate l10n version for fields name, summary and description
277
+     */
278
+    public function applyL10N(array $data, ?string $lang = null): array {
279
+        if ($lang !== '' && $lang !== null) {
280
+            if (isset($data['name']) && is_array($data['name'])) {
281
+                $data['name'] = $this->findBestL10NOption($data['name'], $lang);
282
+            }
283
+            if (isset($data['summary']) && is_array($data['summary'])) {
284
+                $data['summary'] = $this->findBestL10NOption($data['summary'], $lang);
285
+            }
286
+            if (isset($data['description']) && is_array($data['description'])) {
287
+                $data['description'] = trim($this->findBestL10NOption($data['description'], $lang));
288
+            }
289
+        } elseif (isset($data['description']) && is_string($data['description'])) {
290
+            $data['description'] = trim($data['description']);
291
+        } else {
292
+            $data['description'] = '';
293
+        }
294 294
 
295
-		return $data;
296
-	}
295
+        return $data;
296
+    }
297 297
 
298
-	protected function findBestL10NOption(array $options, string $lang): string {
299
-		// only a single option
300
-		if (isset($options['@value'])) {
301
-			return $options['@value'];
302
-		}
298
+    protected function findBestL10NOption(array $options, string $lang): string {
299
+        // only a single option
300
+        if (isset($options['@value'])) {
301
+            return $options['@value'];
302
+        }
303 303
 
304
-		$fallback = $similarLangFallback = $englishFallback = false;
304
+        $fallback = $similarLangFallback = $englishFallback = false;
305 305
 
306
-		$lang = strtolower($lang);
307
-		$similarLang = $lang;
308
-		$pos = strpos($similarLang, '_');
309
-		if ($pos !== false && $pos > 0) {
310
-			// For "de_DE" we want to find "de" and the other way around
311
-			$similarLang = substr($lang, 0, $pos);
312
-		}
306
+        $lang = strtolower($lang);
307
+        $similarLang = $lang;
308
+        $pos = strpos($similarLang, '_');
309
+        if ($pos !== false && $pos > 0) {
310
+            // For "de_DE" we want to find "de" and the other way around
311
+            $similarLang = substr($lang, 0, $pos);
312
+        }
313 313
 
314
-		foreach ($options as $option) {
315
-			if (is_array($option)) {
316
-				if ($fallback === false) {
317
-					$fallback = $option['@value'];
318
-				}
314
+        foreach ($options as $option) {
315
+            if (is_array($option)) {
316
+                if ($fallback === false) {
317
+                    $fallback = $option['@value'];
318
+                }
319 319
 
320
-				if (!isset($option['@attributes']['lang'])) {
321
-					continue;
322
-				}
320
+                if (!isset($option['@attributes']['lang'])) {
321
+                    continue;
322
+                }
323 323
 
324
-				$attributeLang = strtolower($option['@attributes']['lang']);
325
-				if ($attributeLang === $lang) {
326
-					return $option['@value'];
327
-				}
324
+                $attributeLang = strtolower($option['@attributes']['lang']);
325
+                if ($attributeLang === $lang) {
326
+                    return $option['@value'];
327
+                }
328 328
 
329
-				if ($attributeLang === $similarLang) {
330
-					$similarLangFallback = $option['@value'];
331
-				} elseif (str_starts_with($attributeLang, $similarLang . '_')) {
332
-					if ($similarLangFallback === false) {
333
-						$similarLangFallback = $option['@value'];
334
-					}
335
-				}
336
-			} else {
337
-				$englishFallback = $option;
338
-			}
339
-		}
329
+                if ($attributeLang === $similarLang) {
330
+                    $similarLangFallback = $option['@value'];
331
+                } elseif (str_starts_with($attributeLang, $similarLang . '_')) {
332
+                    if ($similarLangFallback === false) {
333
+                        $similarLangFallback = $option['@value'];
334
+                    }
335
+                }
336
+            } else {
337
+                $englishFallback = $option;
338
+            }
339
+        }
340 340
 
341
-		if ($similarLangFallback !== false) {
342
-			return $similarLangFallback;
343
-		} elseif ($englishFallback !== false) {
344
-			return $englishFallback;
345
-		}
346
-		return (string)$fallback;
347
-	}
341
+        if ($similarLangFallback !== false) {
342
+            return $similarLangFallback;
343
+        } elseif ($englishFallback !== false) {
344
+            return $englishFallback;
345
+        }
346
+        return (string)$fallback;
347
+    }
348 348
 }
Please login to merge, or discard this patch.
Spacing   +6 added lines, -6 removed lines patch added patch discarded remove patch
@@ -29,7 +29,7 @@  discard block
 block discarded – undo
29 29
 		}
30 30
 
31 31
 		if ($this->cache !== null) {
32
-			$fileCacheKey = $file . filemtime($file);
32
+			$fileCacheKey = $file.filemtime($file);
33 33
 			if ($cachedValue = $this->cache->get($fileCacheKey)) {
34 34
 				return json_decode($cachedValue, true);
35 35
 			}
@@ -217,10 +217,10 @@  discard block
 block discarded – undo
217 217
 		return isset($data['name'], $data['route']);
218 218
 	}
219 219
 
220
-	public function xmlToArray(\SimpleXMLElement $xml): array|string {
220
+	public function xmlToArray(\SimpleXMLElement $xml): array | string {
221 221
 		$children = $xml->children();
222 222
 		if ($children === null || count($children) === 0) {
223
-			return (string)$xml;
223
+			return (string) $xml;
224 224
 		}
225 225
 
226 226
 		$array = [];
@@ -251,7 +251,7 @@  discard block
 block discarded – undo
251 251
 					if ($attr === null) {
252 252
 						throw new \InvalidArgumentException('xml contains a null element');
253 253
 					}
254
-					$data['@attributes'][$attr] = (string)$value;
254
+					$data['@attributes'][$attr] = (string) $value;
255 255
 				}
256 256
 
257 257
 				if ($totalElement > 1) {
@@ -328,7 +328,7 @@  discard block
 block discarded – undo
328 328
 
329 329
 				if ($attributeLang === $similarLang) {
330 330
 					$similarLangFallback = $option['@value'];
331
-				} elseif (str_starts_with($attributeLang, $similarLang . '_')) {
331
+				} elseif (str_starts_with($attributeLang, $similarLang.'_')) {
332 332
 					if ($similarLangFallback === false) {
333 333
 						$similarLangFallback = $option['@value'];
334 334
 					}
@@ -343,6 +343,6 @@  discard block
 block discarded – undo
343 343
 		} elseif ($englishFallback !== false) {
344 344
 			return $englishFallback;
345 345
 		}
346
-		return (string)$fallback;
346
+		return (string) $fallback;
347 347
 	}
348 348
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_App.php 2 patches
Indentation   +762 added lines, -762 removed lines patch added patch discarded remove patch
@@ -31,766 +31,766 @@
 block discarded – undo
31 31
  * upgrading and removing apps.
32 32
  */
33 33
 class OC_App {
34
-	private static $altLogin = [];
35
-	private static $alreadyRegistered = [];
36
-	public const supportedApp = 300;
37
-	public const officialApp = 200;
38
-
39
-	/**
40
-	 * clean the appId
41
-	 *
42
-	 * @psalm-taint-escape file
43
-	 * @psalm-taint-escape include
44
-	 * @psalm-taint-escape html
45
-	 * @psalm-taint-escape has_quotes
46
-	 *
47
-	 * @deprecated 31.0.0 use IAppManager::cleanAppId
48
-	 */
49
-	public static function cleanAppId(string $app): string {
50
-		return str_replace(['<', '>', '"', "'", '\0', '/', '\\', '..'], '', $app);
51
-	}
52
-
53
-	/**
54
-	 * Check if an app is loaded
55
-	 *
56
-	 * @param string $app
57
-	 * @return bool
58
-	 * @deprecated 27.0.0 use IAppManager::isAppLoaded
59
-	 */
60
-	public static function isAppLoaded(string $app): bool {
61
-		return \OC::$server->get(IAppManager::class)->isAppLoaded($app);
62
-	}
63
-
64
-	/**
65
-	 * loads all apps
66
-	 *
67
-	 * @param string[] $types
68
-	 * @return bool
69
-	 *
70
-	 * This function walks through the Nextcloud directory and loads all apps
71
-	 * it can find. A directory contains an app if the file /appinfo/info.xml
72
-	 * exists.
73
-	 *
74
-	 * if $types is set to non-empty array, only apps of those types will be loaded
75
-	 *
76
-	 * @deprecated 29.0.0 use IAppManager::loadApps instead
77
-	 */
78
-	public static function loadApps(array $types = []): bool {
79
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
80
-			// This should be done before calling this method so that appmanager can be used
81
-			return false;
82
-		}
83
-		return \OC::$server->get(IAppManager::class)->loadApps($types);
84
-	}
85
-
86
-	/**
87
-	 * load a single app
88
-	 *
89
-	 * @param string $app
90
-	 * @throws Exception
91
-	 * @deprecated 27.0.0 use IAppManager::loadApp
92
-	 */
93
-	public static function loadApp(string $app): void {
94
-		\OC::$server->get(IAppManager::class)->loadApp($app);
95
-	}
96
-
97
-	/**
98
-	 * @internal
99
-	 * @param string $app
100
-	 * @param string $path
101
-	 * @param bool $force
102
-	 */
103
-	public static function registerAutoloading(string $app, string $path, bool $force = false) {
104
-		$key = $app . '-' . $path;
105
-		if (!$force && isset(self::$alreadyRegistered[$key])) {
106
-			return;
107
-		}
108
-
109
-		self::$alreadyRegistered[$key] = true;
110
-
111
-		// Register on PSR-4 composer autoloader
112
-		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
113
-		\OC::$server->registerNamespace($app, $appNamespace);
114
-
115
-		if (file_exists($path . '/composer/autoload.php')) {
116
-			require_once $path . '/composer/autoload.php';
117
-		} else {
118
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
119
-		}
120
-
121
-		// Register Test namespace only when testing
122
-		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
123
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
124
-		}
125
-	}
126
-
127
-	/**
128
-	 * check if an app is of a specific type
129
-	 *
130
-	 * @param string $app
131
-	 * @param array $types
132
-	 * @return bool
133
-	 * @deprecated 27.0.0 use IAppManager::isType
134
-	 */
135
-	public static function isType(string $app, array $types): bool {
136
-		return \OC::$server->get(IAppManager::class)->isType($app, $types);
137
-	}
138
-
139
-	/**
140
-	 * read app types from info.xml and cache them in the database
141
-	 */
142
-	public static function setAppTypes(string $app) {
143
-		$appManager = \OC::$server->getAppManager();
144
-		$appData = $appManager->getAppInfo($app);
145
-		if (!is_array($appData)) {
146
-			return;
147
-		}
148
-
149
-		if (isset($appData['types'])) {
150
-			$appTypes = implode(',', $appData['types']);
151
-		} else {
152
-			$appTypes = '';
153
-			$appData['types'] = [];
154
-		}
155
-
156
-		$config = \OC::$server->getConfig();
157
-		$config->setAppValue($app, 'types', $appTypes);
158
-
159
-		if ($appManager->hasProtectedAppType($appData['types'])) {
160
-			$enabled = $config->getAppValue($app, 'enabled', 'yes');
161
-			if ($enabled !== 'yes' && $enabled !== 'no') {
162
-				$config->setAppValue($app, 'enabled', 'yes');
163
-			}
164
-		}
165
-	}
166
-
167
-	/**
168
-	 * Returns apps enabled for the current user.
169
-	 *
170
-	 * @param bool $forceRefresh whether to refresh the cache
171
-	 * @param bool $all whether to return apps for all users, not only the
172
-	 *                  currently logged in one
173
-	 * @return list<string>
174
-	 */
175
-	public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
176
-		if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
177
-			return [];
178
-		}
179
-		// in incognito mode or when logged out, $user will be false,
180
-		// which is also the case during an upgrade
181
-		$appManager = \OC::$server->getAppManager();
182
-		if ($all) {
183
-			$user = null;
184
-		} else {
185
-			$user = \OC::$server->getUserSession()->getUser();
186
-		}
187
-
188
-		if (is_null($user)) {
189
-			$apps = $appManager->getEnabledApps();
190
-		} else {
191
-			$apps = $appManager->getEnabledAppsForUser($user);
192
-		}
193
-		$apps = array_filter($apps, function ($app) {
194
-			return $app !== 'files';//we add this manually
195
-		});
196
-		sort($apps);
197
-		array_unshift($apps, 'files');
198
-		return $apps;
199
-	}
200
-
201
-	/**
202
-	 * enables an app
203
-	 *
204
-	 * @param string $appId
205
-	 * @param array $groups (optional) when set, only these groups will have access to the app
206
-	 * @throws \Exception
207
-	 * @return void
208
-	 *
209
-	 * This function set an app as enabled in appconfig.
210
-	 */
211
-	public function enable(string $appId,
212
-		array $groups = []) {
213
-		// Check if app is already downloaded
214
-		/** @var Installer $installer */
215
-		$installer = Server::get(Installer::class);
216
-		$isDownloaded = $installer->isDownloaded($appId);
217
-
218
-		if (!$isDownloaded) {
219
-			$installer->downloadApp($appId);
220
-		}
221
-
222
-		$installer->installApp($appId);
223
-
224
-		$appManager = \OC::$server->getAppManager();
225
-		if ($groups !== []) {
226
-			$groupManager = \OC::$server->getGroupManager();
227
-			$groupsList = [];
228
-			foreach ($groups as $group) {
229
-				$groupItem = $groupManager->get($group);
230
-				if ($groupItem instanceof \OCP\IGroup) {
231
-					$groupsList[] = $groupManager->get($group);
232
-				}
233
-			}
234
-			$appManager->enableAppForGroups($appId, $groupsList);
235
-		} else {
236
-			$appManager->enableApp($appId);
237
-		}
238
-	}
239
-
240
-	/**
241
-	 * Get the path where to install apps
242
-	 */
243
-	public static function getInstallPath(): ?string {
244
-		foreach (OC::$APPSROOTS as $dir) {
245
-			if (isset($dir['writable']) && $dir['writable'] === true) {
246
-				return $dir['path'];
247
-			}
248
-		}
249
-
250
-		Server::get(LoggerInterface::class)->error('No application directories are marked as writable.', ['app' => 'core']);
251
-		return null;
252
-	}
253
-
254
-
255
-	/**
256
-	 * Find the apps root for an app id.
257
-	 *
258
-	 * If multiple copies are found, the apps root the latest version is returned.
259
-	 *
260
-	 * @param string $appId
261
-	 * @param bool $ignoreCache ignore cache and rebuild it
262
-	 * @return false|array{path: string, url: string} the apps root shape
263
-	 */
264
-	public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
265
-		$sanitizedAppId = self::cleanAppId($appId);
266
-		if ($sanitizedAppId !== $appId) {
267
-			return false;
268
-		}
269
-		static $app_dir = [];
270
-
271
-		if (isset($app_dir[$appId]) && !$ignoreCache) {
272
-			return $app_dir[$appId];
273
-		}
274
-
275
-		$possibleApps = [];
276
-		foreach (OC::$APPSROOTS as $dir) {
277
-			if (file_exists($dir['path'] . '/' . $appId)) {
278
-				$possibleApps[] = $dir;
279
-			}
280
-		}
281
-
282
-		if (empty($possibleApps)) {
283
-			return false;
284
-		} elseif (count($possibleApps) === 1) {
285
-			$dir = array_shift($possibleApps);
286
-			$app_dir[$appId] = $dir;
287
-			return $dir;
288
-		} else {
289
-			$versionToLoad = [];
290
-			foreach ($possibleApps as $possibleApp) {
291
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
292
-				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
293
-					$versionToLoad = [
294
-						'dir' => $possibleApp,
295
-						'version' => $version,
296
-					];
297
-				}
298
-			}
299
-			$app_dir[$appId] = $versionToLoad['dir'];
300
-			return $versionToLoad['dir'];
301
-			//TODO - write test
302
-		}
303
-	}
304
-
305
-	/**
306
-	 * Get the directory for the given app.
307
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
308
-	 *
309
-	 * @psalm-taint-specialize
310
-	 *
311
-	 * @param string $appId
312
-	 * @param bool $refreshAppPath should be set to true only during install/upgrade
313
-	 * @return string|false
314
-	 * @deprecated 11.0.0 use Server::get(IAppManager)->getAppPath()
315
-	 */
316
-	public static function getAppPath(string $appId, bool $refreshAppPath = false) {
317
-		$appId = self::cleanAppId($appId);
318
-		if ($appId === '') {
319
-			return false;
320
-		} elseif ($appId === 'core') {
321
-			return __DIR__ . '/../../../core';
322
-		}
323
-
324
-		if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
325
-			return $dir['path'] . '/' . $appId;
326
-		}
327
-		return false;
328
-	}
329
-
330
-	/**
331
-	 * Get the path for the given app on the access
332
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
333
-	 *
334
-	 * @param string $appId
335
-	 * @return string|false
336
-	 * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
337
-	 */
338
-	public static function getAppWebPath(string $appId) {
339
-		if (($dir = self::findAppInDirectories($appId)) != false) {
340
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
341
-		}
342
-		return false;
343
-	}
344
-
345
-	/**
346
-	 * get app's version based on it's path
347
-	 *
348
-	 * @param string $path
349
-	 * @return string
350
-	 */
351
-	public static function getAppVersionByPath(string $path): string {
352
-		$infoFile = $path . '/appinfo/info.xml';
353
-		$appData = Server::get(IAppManager::class)->getAppInfoByPath($infoFile);
354
-		return $appData['version'] ?? '';
355
-	}
356
-
357
-	/**
358
-	 * get the id of loaded app
359
-	 *
360
-	 * @return string
361
-	 */
362
-	public static function getCurrentApp(): string {
363
-		if (\OC::$CLI) {
364
-			return '';
365
-		}
366
-
367
-		$request = \OC::$server->getRequest();
368
-		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
369
-		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
370
-		if (empty($topFolder)) {
371
-			try {
372
-				$path_info = $request->getPathInfo();
373
-			} catch (Exception $e) {
374
-				// Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
375
-				\OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
376
-				return '';
377
-			}
378
-			if ($path_info) {
379
-				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
380
-			}
381
-		}
382
-		if ($topFolder == 'apps') {
383
-			$length = strlen($topFolder);
384
-			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
385
-		} else {
386
-			return $topFolder;
387
-		}
388
-	}
389
-
390
-	/**
391
-	 * @param array $entry
392
-	 * @deprecated 20.0.0 Please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface
393
-	 */
394
-	public static function registerLogIn(array $entry) {
395
-		Server::get(LoggerInterface::class)->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface');
396
-		self::$altLogin[] = $entry;
397
-	}
398
-
399
-	/**
400
-	 * @return array
401
-	 */
402
-	public static function getAlternativeLogIns(): array {
403
-		/** @var Coordinator $bootstrapCoordinator */
404
-		$bootstrapCoordinator = Server::get(Coordinator::class);
405
-
406
-		foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
407
-			if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
408
-				Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
409
-					'option' => $registration->getService(),
410
-					'interface' => IAlternativeLogin::class,
411
-					'app' => $registration->getAppId(),
412
-				]);
413
-				continue;
414
-			}
415
-
416
-			try {
417
-				/** @var IAlternativeLogin $provider */
418
-				$provider = Server::get($registration->getService());
419
-			} catch (ContainerExceptionInterface $e) {
420
-				Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.',
421
-					[
422
-						'exception' => $e,
423
-						'option' => $registration->getService(),
424
-						'app' => $registration->getAppId(),
425
-					]);
426
-			}
427
-
428
-			try {
429
-				$provider->load();
430
-
431
-				self::$altLogin[] = [
432
-					'name' => $provider->getLabel(),
433
-					'href' => $provider->getLink(),
434
-					'class' => $provider->getClass(),
435
-				];
436
-			} catch (Throwable $e) {
437
-				Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.',
438
-					[
439
-						'exception' => $e,
440
-						'option' => $registration->getService(),
441
-						'app' => $registration->getAppId(),
442
-					]);
443
-			}
444
-		}
445
-
446
-		return self::$altLogin;
447
-	}
448
-
449
-	/**
450
-	 * get a list of all apps in the apps folder
451
-	 *
452
-	 * @return string[] an array of app names (string IDs)
453
-	 * @deprecated 31.0.0 Use IAppManager::getAllAppsInAppsFolders instead
454
-	 */
455
-	public static function getAllApps(): array {
456
-		return Server::get(IAppManager::class)->getAllAppsInAppsFolders();
457
-	}
458
-
459
-	/**
460
-	 * List all supported apps
461
-	 *
462
-	 * @deprecated 32.0.0 Use \OCP\Support\Subscription\IRegistry::delegateGetSupportedApps instead
463
-	 */
464
-	public function getSupportedApps(): array {
465
-		$subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
466
-		$supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
467
-		return $supportedApps;
468
-	}
469
-
470
-	/**
471
-	 * List all apps, this is used in apps.php
472
-	 *
473
-	 * @return array
474
-	 */
475
-	public function listAllApps(): array {
476
-		$appManager = \OC::$server->getAppManager();
477
-
478
-		$installedApps = $appManager->getAllAppsInAppsFolders();
479
-		//we don't want to show configuration for these
480
-		$blacklist = $appManager->getAlwaysEnabledApps();
481
-		$appList = [];
482
-		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
483
-		$urlGenerator = \OC::$server->getURLGenerator();
484
-		$supportedApps = $this->getSupportedApps();
485
-
486
-		foreach ($installedApps as $app) {
487
-			if (!in_array($app, $blacklist)) {
488
-				$info = $appManager->getAppInfo($app, false, $langCode);
489
-				if (!is_array($info)) {
490
-					Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
491
-					continue;
492
-				}
493
-
494
-				if (!isset($info['name'])) {
495
-					Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
496
-					continue;
497
-				}
498
-
499
-				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
500
-				$info['groups'] = null;
501
-				if ($enabled === 'yes') {
502
-					$active = true;
503
-				} elseif ($enabled === 'no') {
504
-					$active = false;
505
-				} else {
506
-					$active = true;
507
-					$info['groups'] = $enabled;
508
-				}
509
-
510
-				$info['active'] = $active;
511
-
512
-				if ($appManager->isShipped($app)) {
513
-					$info['internal'] = true;
514
-					$info['level'] = self::officialApp;
515
-					$info['removable'] = false;
516
-				} else {
517
-					$info['internal'] = false;
518
-					$info['removable'] = true;
519
-				}
520
-
521
-				if (in_array($app, $supportedApps)) {
522
-					$info['level'] = self::supportedApp;
523
-				}
524
-
525
-				$appPath = self::getAppPath($app);
526
-				if ($appPath !== false) {
527
-					$appIcon = $appPath . '/img/' . $app . '.svg';
528
-					if (file_exists($appIcon)) {
529
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
530
-						$info['previewAsIcon'] = true;
531
-					} else {
532
-						$appIcon = $appPath . '/img/app.svg';
533
-						if (file_exists($appIcon)) {
534
-							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
535
-							$info['previewAsIcon'] = true;
536
-						}
537
-					}
538
-				}
539
-				// fix documentation
540
-				if (isset($info['documentation']) && is_array($info['documentation'])) {
541
-					foreach ($info['documentation'] as $key => $url) {
542
-						// If it is not an absolute URL we assume it is a key
543
-						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
544
-						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
545
-							$url = $urlGenerator->linkToDocs($url);
546
-						}
547
-
548
-						$info['documentation'][$key] = $url;
549
-					}
550
-				}
551
-
552
-				$info['version'] = $appManager->getAppVersion($app);
553
-				$appList[] = $info;
554
-			}
555
-		}
556
-
557
-		return $appList;
558
-	}
559
-
560
-	public static function shouldUpgrade(string $app): bool {
561
-		$versions = self::getAppVersions();
562
-		$currentVersion = Server::get(\OCP\App\IAppManager::class)->getAppVersion($app);
563
-		if ($currentVersion && isset($versions[$app])) {
564
-			$installedVersion = $versions[$app];
565
-			if (!version_compare($currentVersion, $installedVersion, '=')) {
566
-				return true;
567
-			}
568
-		}
569
-		return false;
570
-	}
571
-
572
-	/**
573
-	 * Adjust the number of version parts of $version1 to match
574
-	 * the number of version parts of $version2.
575
-	 *
576
-	 * @param string $version1 version to adjust
577
-	 * @param string $version2 version to take the number of parts from
578
-	 * @return string shortened $version1
579
-	 */
580
-	private static function adjustVersionParts(string $version1, string $version2): string {
581
-		$version1 = explode('.', $version1);
582
-		$version2 = explode('.', $version2);
583
-		// reduce $version1 to match the number of parts in $version2
584
-		while (count($version1) > count($version2)) {
585
-			array_pop($version1);
586
-		}
587
-		// if $version1 does not have enough parts, add some
588
-		while (count($version1) < count($version2)) {
589
-			$version1[] = '0';
590
-		}
591
-		return implode('.', $version1);
592
-	}
593
-
594
-	/**
595
-	 * Check whether the current Nextcloud version matches the given
596
-	 * application's version requirements.
597
-	 *
598
-	 * The comparison is made based on the number of parts that the
599
-	 * app info version has. For example for ownCloud 6.0.3 if the
600
-	 * app info version is expecting version 6.0, the comparison is
601
-	 * made on the first two parts of the ownCloud version.
602
-	 * This means that it's possible to specify "requiremin" => 6
603
-	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
604
-	 *
605
-	 * @param string $ocVersion Nextcloud version to check against
606
-	 * @param array $appInfo app info (from xml)
607
-	 *
608
-	 * @return boolean true if compatible, otherwise false
609
-	 */
610
-	public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
611
-		$requireMin = '';
612
-		$requireMax = '';
613
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
614
-			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
615
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
616
-			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
617
-		} elseif (isset($appInfo['requiremin'])) {
618
-			$requireMin = $appInfo['requiremin'];
619
-		} elseif (isset($appInfo['require'])) {
620
-			$requireMin = $appInfo['require'];
621
-		}
622
-
623
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
624
-			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
625
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
626
-			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
627
-		} elseif (isset($appInfo['requiremax'])) {
628
-			$requireMax = $appInfo['requiremax'];
629
-		}
630
-
631
-		if (!empty($requireMin)
632
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
633
-		) {
634
-			return false;
635
-		}
636
-
637
-		if (!$ignoreMax && !empty($requireMax)
638
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
639
-		) {
640
-			return false;
641
-		}
642
-
643
-		return true;
644
-	}
645
-
646
-	/**
647
-	 * get the installed version of all apps
648
-	 * @deprecated 32.0.0 Use IAppManager::getAppInstalledVersions or IAppConfig::getAppInstalledVersions instead
649
-	 */
650
-	public static function getAppVersions(): array {
651
-		return Server::get(IAppConfig::class)->getAppInstalledVersions();
652
-	}
653
-
654
-	/**
655
-	 * update the database for the app and call the update script
656
-	 *
657
-	 * @param string $appId
658
-	 * @return bool
659
-	 */
660
-	public static function updateApp(string $appId): bool {
661
-		// for apps distributed with core, we refresh app path in case the downloaded version
662
-		// have been installed in custom apps and not in the default path
663
-		$appPath = self::getAppPath($appId, true);
664
-		if ($appPath === false) {
665
-			return false;
666
-		}
667
-
668
-		if (is_file($appPath . '/appinfo/database.xml')) {
669
-			Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
670
-			return false;
671
-		}
672
-
673
-		\OC::$server->getAppManager()->clearAppsCache();
674
-		$l = \OC::$server->getL10N('core');
675
-		$appData = Server::get(\OCP\App\IAppManager::class)->getAppInfo($appId, false, $l->getLanguageCode());
676
-
677
-		$ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
678
-		$ignoreMax = in_array($appId, $ignoreMaxApps, true);
679
-		\OC_App::checkAppDependencies(
680
-			\OC::$server->getConfig(),
681
-			$l,
682
-			$appData,
683
-			$ignoreMax
684
-		);
685
-
686
-		self::registerAutoloading($appId, $appPath, true);
687
-		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
688
-
689
-		$ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
690
-		$ms->migrate();
691
-
692
-		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
693
-		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
694
-		// update appversion in app manager
695
-		\OC::$server->getAppManager()->clearAppsCache();
696
-		\OC::$server->getAppManager()->getAppVersion($appId, false);
697
-
698
-		self::setupBackgroundJobs($appData['background-jobs']);
699
-
700
-		//set remote/public handlers
701
-		if (array_key_exists('ocsid', $appData)) {
702
-			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
703
-		} elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
704
-			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
705
-		}
706
-		foreach ($appData['remote'] as $name => $path) {
707
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
708
-		}
709
-		foreach ($appData['public'] as $name => $path) {
710
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
711
-		}
712
-
713
-		self::setAppTypes($appId);
714
-
715
-		$version = Server::get(\OCP\App\IAppManager::class)->getAppVersion($appId);
716
-		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
717
-
718
-		// migrate eventual new config keys in the process
719
-		/** @psalm-suppress InternalMethod */
720
-		Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId);
721
-
722
-		\OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
723
-		\OC::$server->get(IEventDispatcher::class)->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
724
-			ManagerEvent::EVENT_APP_UPDATE, $appId
725
-		));
726
-
727
-		return true;
728
-	}
729
-
730
-	/**
731
-	 * @param string $appId
732
-	 * @param string[] $steps
733
-	 * @throws \OC\NeedsUpdateException
734
-	 */
735
-	public static function executeRepairSteps(string $appId, array $steps) {
736
-		if (empty($steps)) {
737
-			return;
738
-		}
739
-		// load the app
740
-		self::loadApp($appId);
741
-
742
-		$dispatcher = Server::get(IEventDispatcher::class);
743
-
744
-		// load the steps
745
-		$r = Server::get(Repair::class);
746
-		foreach ($steps as $step) {
747
-			try {
748
-				$r->addStep($step);
749
-			} catch (Exception $ex) {
750
-				$dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
751
-				logger('core')->error('Failed to add app migration step ' . $step, ['exception' => $ex]);
752
-			}
753
-		}
754
-		// run the steps
755
-		$r->run();
756
-	}
757
-
758
-	public static function setupBackgroundJobs(array $jobs) {
759
-		$queue = \OC::$server->getJobList();
760
-		foreach ($jobs as $job) {
761
-			$queue->add($job);
762
-		}
763
-	}
764
-
765
-	/**
766
-	 * @param string $appId
767
-	 * @param string[] $steps
768
-	 */
769
-	private static function setupLiveMigrations(string $appId, array $steps) {
770
-		$queue = \OC::$server->getJobList();
771
-		foreach ($steps as $step) {
772
-			$queue->add('OC\Migration\BackgroundRepair', [
773
-				'app' => $appId,
774
-				'step' => $step]);
775
-		}
776
-	}
777
-
778
-	/**
779
-	 * @param \OCP\IConfig $config
780
-	 * @param \OCP\IL10N $l
781
-	 * @param array $info
782
-	 * @throws \Exception
783
-	 */
784
-	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
785
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
786
-		$missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
787
-		if (!empty($missing)) {
788
-			$missingMsg = implode(PHP_EOL, $missing);
789
-			throw new \Exception(
790
-				$l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
791
-					[$info['name'], $missingMsg]
792
-				)
793
-			);
794
-		}
795
-	}
34
+    private static $altLogin = [];
35
+    private static $alreadyRegistered = [];
36
+    public const supportedApp = 300;
37
+    public const officialApp = 200;
38
+
39
+    /**
40
+     * clean the appId
41
+     *
42
+     * @psalm-taint-escape file
43
+     * @psalm-taint-escape include
44
+     * @psalm-taint-escape html
45
+     * @psalm-taint-escape has_quotes
46
+     *
47
+     * @deprecated 31.0.0 use IAppManager::cleanAppId
48
+     */
49
+    public static function cleanAppId(string $app): string {
50
+        return str_replace(['<', '>', '"', "'", '\0', '/', '\\', '..'], '', $app);
51
+    }
52
+
53
+    /**
54
+     * Check if an app is loaded
55
+     *
56
+     * @param string $app
57
+     * @return bool
58
+     * @deprecated 27.0.0 use IAppManager::isAppLoaded
59
+     */
60
+    public static function isAppLoaded(string $app): bool {
61
+        return \OC::$server->get(IAppManager::class)->isAppLoaded($app);
62
+    }
63
+
64
+    /**
65
+     * loads all apps
66
+     *
67
+     * @param string[] $types
68
+     * @return bool
69
+     *
70
+     * This function walks through the Nextcloud directory and loads all apps
71
+     * it can find. A directory contains an app if the file /appinfo/info.xml
72
+     * exists.
73
+     *
74
+     * if $types is set to non-empty array, only apps of those types will be loaded
75
+     *
76
+     * @deprecated 29.0.0 use IAppManager::loadApps instead
77
+     */
78
+    public static function loadApps(array $types = []): bool {
79
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
80
+            // This should be done before calling this method so that appmanager can be used
81
+            return false;
82
+        }
83
+        return \OC::$server->get(IAppManager::class)->loadApps($types);
84
+    }
85
+
86
+    /**
87
+     * load a single app
88
+     *
89
+     * @param string $app
90
+     * @throws Exception
91
+     * @deprecated 27.0.0 use IAppManager::loadApp
92
+     */
93
+    public static function loadApp(string $app): void {
94
+        \OC::$server->get(IAppManager::class)->loadApp($app);
95
+    }
96
+
97
+    /**
98
+     * @internal
99
+     * @param string $app
100
+     * @param string $path
101
+     * @param bool $force
102
+     */
103
+    public static function registerAutoloading(string $app, string $path, bool $force = false) {
104
+        $key = $app . '-' . $path;
105
+        if (!$force && isset(self::$alreadyRegistered[$key])) {
106
+            return;
107
+        }
108
+
109
+        self::$alreadyRegistered[$key] = true;
110
+
111
+        // Register on PSR-4 composer autoloader
112
+        $appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
113
+        \OC::$server->registerNamespace($app, $appNamespace);
114
+
115
+        if (file_exists($path . '/composer/autoload.php')) {
116
+            require_once $path . '/composer/autoload.php';
117
+        } else {
118
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
119
+        }
120
+
121
+        // Register Test namespace only when testing
122
+        if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
123
+            \OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
124
+        }
125
+    }
126
+
127
+    /**
128
+     * check if an app is of a specific type
129
+     *
130
+     * @param string $app
131
+     * @param array $types
132
+     * @return bool
133
+     * @deprecated 27.0.0 use IAppManager::isType
134
+     */
135
+    public static function isType(string $app, array $types): bool {
136
+        return \OC::$server->get(IAppManager::class)->isType($app, $types);
137
+    }
138
+
139
+    /**
140
+     * read app types from info.xml and cache them in the database
141
+     */
142
+    public static function setAppTypes(string $app) {
143
+        $appManager = \OC::$server->getAppManager();
144
+        $appData = $appManager->getAppInfo($app);
145
+        if (!is_array($appData)) {
146
+            return;
147
+        }
148
+
149
+        if (isset($appData['types'])) {
150
+            $appTypes = implode(',', $appData['types']);
151
+        } else {
152
+            $appTypes = '';
153
+            $appData['types'] = [];
154
+        }
155
+
156
+        $config = \OC::$server->getConfig();
157
+        $config->setAppValue($app, 'types', $appTypes);
158
+
159
+        if ($appManager->hasProtectedAppType($appData['types'])) {
160
+            $enabled = $config->getAppValue($app, 'enabled', 'yes');
161
+            if ($enabled !== 'yes' && $enabled !== 'no') {
162
+                $config->setAppValue($app, 'enabled', 'yes');
163
+            }
164
+        }
165
+    }
166
+
167
+    /**
168
+     * Returns apps enabled for the current user.
169
+     *
170
+     * @param bool $forceRefresh whether to refresh the cache
171
+     * @param bool $all whether to return apps for all users, not only the
172
+     *                  currently logged in one
173
+     * @return list<string>
174
+     */
175
+    public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
176
+        if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
177
+            return [];
178
+        }
179
+        // in incognito mode or when logged out, $user will be false,
180
+        // which is also the case during an upgrade
181
+        $appManager = \OC::$server->getAppManager();
182
+        if ($all) {
183
+            $user = null;
184
+        } else {
185
+            $user = \OC::$server->getUserSession()->getUser();
186
+        }
187
+
188
+        if (is_null($user)) {
189
+            $apps = $appManager->getEnabledApps();
190
+        } else {
191
+            $apps = $appManager->getEnabledAppsForUser($user);
192
+        }
193
+        $apps = array_filter($apps, function ($app) {
194
+            return $app !== 'files';//we add this manually
195
+        });
196
+        sort($apps);
197
+        array_unshift($apps, 'files');
198
+        return $apps;
199
+    }
200
+
201
+    /**
202
+     * enables an app
203
+     *
204
+     * @param string $appId
205
+     * @param array $groups (optional) when set, only these groups will have access to the app
206
+     * @throws \Exception
207
+     * @return void
208
+     *
209
+     * This function set an app as enabled in appconfig.
210
+     */
211
+    public function enable(string $appId,
212
+        array $groups = []) {
213
+        // Check if app is already downloaded
214
+        /** @var Installer $installer */
215
+        $installer = Server::get(Installer::class);
216
+        $isDownloaded = $installer->isDownloaded($appId);
217
+
218
+        if (!$isDownloaded) {
219
+            $installer->downloadApp($appId);
220
+        }
221
+
222
+        $installer->installApp($appId);
223
+
224
+        $appManager = \OC::$server->getAppManager();
225
+        if ($groups !== []) {
226
+            $groupManager = \OC::$server->getGroupManager();
227
+            $groupsList = [];
228
+            foreach ($groups as $group) {
229
+                $groupItem = $groupManager->get($group);
230
+                if ($groupItem instanceof \OCP\IGroup) {
231
+                    $groupsList[] = $groupManager->get($group);
232
+                }
233
+            }
234
+            $appManager->enableAppForGroups($appId, $groupsList);
235
+        } else {
236
+            $appManager->enableApp($appId);
237
+        }
238
+    }
239
+
240
+    /**
241
+     * Get the path where to install apps
242
+     */
243
+    public static function getInstallPath(): ?string {
244
+        foreach (OC::$APPSROOTS as $dir) {
245
+            if (isset($dir['writable']) && $dir['writable'] === true) {
246
+                return $dir['path'];
247
+            }
248
+        }
249
+
250
+        Server::get(LoggerInterface::class)->error('No application directories are marked as writable.', ['app' => 'core']);
251
+        return null;
252
+    }
253
+
254
+
255
+    /**
256
+     * Find the apps root for an app id.
257
+     *
258
+     * If multiple copies are found, the apps root the latest version is returned.
259
+     *
260
+     * @param string $appId
261
+     * @param bool $ignoreCache ignore cache and rebuild it
262
+     * @return false|array{path: string, url: string} the apps root shape
263
+     */
264
+    public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
265
+        $sanitizedAppId = self::cleanAppId($appId);
266
+        if ($sanitizedAppId !== $appId) {
267
+            return false;
268
+        }
269
+        static $app_dir = [];
270
+
271
+        if (isset($app_dir[$appId]) && !$ignoreCache) {
272
+            return $app_dir[$appId];
273
+        }
274
+
275
+        $possibleApps = [];
276
+        foreach (OC::$APPSROOTS as $dir) {
277
+            if (file_exists($dir['path'] . '/' . $appId)) {
278
+                $possibleApps[] = $dir;
279
+            }
280
+        }
281
+
282
+        if (empty($possibleApps)) {
283
+            return false;
284
+        } elseif (count($possibleApps) === 1) {
285
+            $dir = array_shift($possibleApps);
286
+            $app_dir[$appId] = $dir;
287
+            return $dir;
288
+        } else {
289
+            $versionToLoad = [];
290
+            foreach ($possibleApps as $possibleApp) {
291
+                $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
292
+                if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
293
+                    $versionToLoad = [
294
+                        'dir' => $possibleApp,
295
+                        'version' => $version,
296
+                    ];
297
+                }
298
+            }
299
+            $app_dir[$appId] = $versionToLoad['dir'];
300
+            return $versionToLoad['dir'];
301
+            //TODO - write test
302
+        }
303
+    }
304
+
305
+    /**
306
+     * Get the directory for the given app.
307
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
308
+     *
309
+     * @psalm-taint-specialize
310
+     *
311
+     * @param string $appId
312
+     * @param bool $refreshAppPath should be set to true only during install/upgrade
313
+     * @return string|false
314
+     * @deprecated 11.0.0 use Server::get(IAppManager)->getAppPath()
315
+     */
316
+    public static function getAppPath(string $appId, bool $refreshAppPath = false) {
317
+        $appId = self::cleanAppId($appId);
318
+        if ($appId === '') {
319
+            return false;
320
+        } elseif ($appId === 'core') {
321
+            return __DIR__ . '/../../../core';
322
+        }
323
+
324
+        if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
325
+            return $dir['path'] . '/' . $appId;
326
+        }
327
+        return false;
328
+    }
329
+
330
+    /**
331
+     * Get the path for the given app on the access
332
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
333
+     *
334
+     * @param string $appId
335
+     * @return string|false
336
+     * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
337
+     */
338
+    public static function getAppWebPath(string $appId) {
339
+        if (($dir = self::findAppInDirectories($appId)) != false) {
340
+            return OC::$WEBROOT . $dir['url'] . '/' . $appId;
341
+        }
342
+        return false;
343
+    }
344
+
345
+    /**
346
+     * get app's version based on it's path
347
+     *
348
+     * @param string $path
349
+     * @return string
350
+     */
351
+    public static function getAppVersionByPath(string $path): string {
352
+        $infoFile = $path . '/appinfo/info.xml';
353
+        $appData = Server::get(IAppManager::class)->getAppInfoByPath($infoFile);
354
+        return $appData['version'] ?? '';
355
+    }
356
+
357
+    /**
358
+     * get the id of loaded app
359
+     *
360
+     * @return string
361
+     */
362
+    public static function getCurrentApp(): string {
363
+        if (\OC::$CLI) {
364
+            return '';
365
+        }
366
+
367
+        $request = \OC::$server->getRequest();
368
+        $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
369
+        $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
370
+        if (empty($topFolder)) {
371
+            try {
372
+                $path_info = $request->getPathInfo();
373
+            } catch (Exception $e) {
374
+                // Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
375
+                \OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
376
+                return '';
377
+            }
378
+            if ($path_info) {
379
+                $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
380
+            }
381
+        }
382
+        if ($topFolder == 'apps') {
383
+            $length = strlen($topFolder);
384
+            return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
385
+        } else {
386
+            return $topFolder;
387
+        }
388
+    }
389
+
390
+    /**
391
+     * @param array $entry
392
+     * @deprecated 20.0.0 Please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface
393
+     */
394
+    public static function registerLogIn(array $entry) {
395
+        Server::get(LoggerInterface::class)->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface');
396
+        self::$altLogin[] = $entry;
397
+    }
398
+
399
+    /**
400
+     * @return array
401
+     */
402
+    public static function getAlternativeLogIns(): array {
403
+        /** @var Coordinator $bootstrapCoordinator */
404
+        $bootstrapCoordinator = Server::get(Coordinator::class);
405
+
406
+        foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
407
+            if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
408
+                Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
409
+                    'option' => $registration->getService(),
410
+                    'interface' => IAlternativeLogin::class,
411
+                    'app' => $registration->getAppId(),
412
+                ]);
413
+                continue;
414
+            }
415
+
416
+            try {
417
+                /** @var IAlternativeLogin $provider */
418
+                $provider = Server::get($registration->getService());
419
+            } catch (ContainerExceptionInterface $e) {
420
+                Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.',
421
+                    [
422
+                        'exception' => $e,
423
+                        'option' => $registration->getService(),
424
+                        'app' => $registration->getAppId(),
425
+                    ]);
426
+            }
427
+
428
+            try {
429
+                $provider->load();
430
+
431
+                self::$altLogin[] = [
432
+                    'name' => $provider->getLabel(),
433
+                    'href' => $provider->getLink(),
434
+                    'class' => $provider->getClass(),
435
+                ];
436
+            } catch (Throwable $e) {
437
+                Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.',
438
+                    [
439
+                        'exception' => $e,
440
+                        'option' => $registration->getService(),
441
+                        'app' => $registration->getAppId(),
442
+                    ]);
443
+            }
444
+        }
445
+
446
+        return self::$altLogin;
447
+    }
448
+
449
+    /**
450
+     * get a list of all apps in the apps folder
451
+     *
452
+     * @return string[] an array of app names (string IDs)
453
+     * @deprecated 31.0.0 Use IAppManager::getAllAppsInAppsFolders instead
454
+     */
455
+    public static function getAllApps(): array {
456
+        return Server::get(IAppManager::class)->getAllAppsInAppsFolders();
457
+    }
458
+
459
+    /**
460
+     * List all supported apps
461
+     *
462
+     * @deprecated 32.0.0 Use \OCP\Support\Subscription\IRegistry::delegateGetSupportedApps instead
463
+     */
464
+    public function getSupportedApps(): array {
465
+        $subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
466
+        $supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
467
+        return $supportedApps;
468
+    }
469
+
470
+    /**
471
+     * List all apps, this is used in apps.php
472
+     *
473
+     * @return array
474
+     */
475
+    public function listAllApps(): array {
476
+        $appManager = \OC::$server->getAppManager();
477
+
478
+        $installedApps = $appManager->getAllAppsInAppsFolders();
479
+        //we don't want to show configuration for these
480
+        $blacklist = $appManager->getAlwaysEnabledApps();
481
+        $appList = [];
482
+        $langCode = \OC::$server->getL10N('core')->getLanguageCode();
483
+        $urlGenerator = \OC::$server->getURLGenerator();
484
+        $supportedApps = $this->getSupportedApps();
485
+
486
+        foreach ($installedApps as $app) {
487
+            if (!in_array($app, $blacklist)) {
488
+                $info = $appManager->getAppInfo($app, false, $langCode);
489
+                if (!is_array($info)) {
490
+                    Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
491
+                    continue;
492
+                }
493
+
494
+                if (!isset($info['name'])) {
495
+                    Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
496
+                    continue;
497
+                }
498
+
499
+                $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
500
+                $info['groups'] = null;
501
+                if ($enabled === 'yes') {
502
+                    $active = true;
503
+                } elseif ($enabled === 'no') {
504
+                    $active = false;
505
+                } else {
506
+                    $active = true;
507
+                    $info['groups'] = $enabled;
508
+                }
509
+
510
+                $info['active'] = $active;
511
+
512
+                if ($appManager->isShipped($app)) {
513
+                    $info['internal'] = true;
514
+                    $info['level'] = self::officialApp;
515
+                    $info['removable'] = false;
516
+                } else {
517
+                    $info['internal'] = false;
518
+                    $info['removable'] = true;
519
+                }
520
+
521
+                if (in_array($app, $supportedApps)) {
522
+                    $info['level'] = self::supportedApp;
523
+                }
524
+
525
+                $appPath = self::getAppPath($app);
526
+                if ($appPath !== false) {
527
+                    $appIcon = $appPath . '/img/' . $app . '.svg';
528
+                    if (file_exists($appIcon)) {
529
+                        $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
530
+                        $info['previewAsIcon'] = true;
531
+                    } else {
532
+                        $appIcon = $appPath . '/img/app.svg';
533
+                        if (file_exists($appIcon)) {
534
+                            $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
535
+                            $info['previewAsIcon'] = true;
536
+                        }
537
+                    }
538
+                }
539
+                // fix documentation
540
+                if (isset($info['documentation']) && is_array($info['documentation'])) {
541
+                    foreach ($info['documentation'] as $key => $url) {
542
+                        // If it is not an absolute URL we assume it is a key
543
+                        // i.e. admin-ldap will get converted to go.php?to=admin-ldap
544
+                        if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
545
+                            $url = $urlGenerator->linkToDocs($url);
546
+                        }
547
+
548
+                        $info['documentation'][$key] = $url;
549
+                    }
550
+                }
551
+
552
+                $info['version'] = $appManager->getAppVersion($app);
553
+                $appList[] = $info;
554
+            }
555
+        }
556
+
557
+        return $appList;
558
+    }
559
+
560
+    public static function shouldUpgrade(string $app): bool {
561
+        $versions = self::getAppVersions();
562
+        $currentVersion = Server::get(\OCP\App\IAppManager::class)->getAppVersion($app);
563
+        if ($currentVersion && isset($versions[$app])) {
564
+            $installedVersion = $versions[$app];
565
+            if (!version_compare($currentVersion, $installedVersion, '=')) {
566
+                return true;
567
+            }
568
+        }
569
+        return false;
570
+    }
571
+
572
+    /**
573
+     * Adjust the number of version parts of $version1 to match
574
+     * the number of version parts of $version2.
575
+     *
576
+     * @param string $version1 version to adjust
577
+     * @param string $version2 version to take the number of parts from
578
+     * @return string shortened $version1
579
+     */
580
+    private static function adjustVersionParts(string $version1, string $version2): string {
581
+        $version1 = explode('.', $version1);
582
+        $version2 = explode('.', $version2);
583
+        // reduce $version1 to match the number of parts in $version2
584
+        while (count($version1) > count($version2)) {
585
+            array_pop($version1);
586
+        }
587
+        // if $version1 does not have enough parts, add some
588
+        while (count($version1) < count($version2)) {
589
+            $version1[] = '0';
590
+        }
591
+        return implode('.', $version1);
592
+    }
593
+
594
+    /**
595
+     * Check whether the current Nextcloud version matches the given
596
+     * application's version requirements.
597
+     *
598
+     * The comparison is made based on the number of parts that the
599
+     * app info version has. For example for ownCloud 6.0.3 if the
600
+     * app info version is expecting version 6.0, the comparison is
601
+     * made on the first two parts of the ownCloud version.
602
+     * This means that it's possible to specify "requiremin" => 6
603
+     * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
604
+     *
605
+     * @param string $ocVersion Nextcloud version to check against
606
+     * @param array $appInfo app info (from xml)
607
+     *
608
+     * @return boolean true if compatible, otherwise false
609
+     */
610
+    public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
611
+        $requireMin = '';
612
+        $requireMax = '';
613
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
614
+            $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
615
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
616
+            $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
617
+        } elseif (isset($appInfo['requiremin'])) {
618
+            $requireMin = $appInfo['requiremin'];
619
+        } elseif (isset($appInfo['require'])) {
620
+            $requireMin = $appInfo['require'];
621
+        }
622
+
623
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
624
+            $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
625
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
626
+            $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
627
+        } elseif (isset($appInfo['requiremax'])) {
628
+            $requireMax = $appInfo['requiremax'];
629
+        }
630
+
631
+        if (!empty($requireMin)
632
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
633
+        ) {
634
+            return false;
635
+        }
636
+
637
+        if (!$ignoreMax && !empty($requireMax)
638
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
639
+        ) {
640
+            return false;
641
+        }
642
+
643
+        return true;
644
+    }
645
+
646
+    /**
647
+     * get the installed version of all apps
648
+     * @deprecated 32.0.0 Use IAppManager::getAppInstalledVersions or IAppConfig::getAppInstalledVersions instead
649
+     */
650
+    public static function getAppVersions(): array {
651
+        return Server::get(IAppConfig::class)->getAppInstalledVersions();
652
+    }
653
+
654
+    /**
655
+     * update the database for the app and call the update script
656
+     *
657
+     * @param string $appId
658
+     * @return bool
659
+     */
660
+    public static function updateApp(string $appId): bool {
661
+        // for apps distributed with core, we refresh app path in case the downloaded version
662
+        // have been installed in custom apps and not in the default path
663
+        $appPath = self::getAppPath($appId, true);
664
+        if ($appPath === false) {
665
+            return false;
666
+        }
667
+
668
+        if (is_file($appPath . '/appinfo/database.xml')) {
669
+            Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
670
+            return false;
671
+        }
672
+
673
+        \OC::$server->getAppManager()->clearAppsCache();
674
+        $l = \OC::$server->getL10N('core');
675
+        $appData = Server::get(\OCP\App\IAppManager::class)->getAppInfo($appId, false, $l->getLanguageCode());
676
+
677
+        $ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
678
+        $ignoreMax = in_array($appId, $ignoreMaxApps, true);
679
+        \OC_App::checkAppDependencies(
680
+            \OC::$server->getConfig(),
681
+            $l,
682
+            $appData,
683
+            $ignoreMax
684
+        );
685
+
686
+        self::registerAutoloading($appId, $appPath, true);
687
+        self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
688
+
689
+        $ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
690
+        $ms->migrate();
691
+
692
+        self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
693
+        self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
694
+        // update appversion in app manager
695
+        \OC::$server->getAppManager()->clearAppsCache();
696
+        \OC::$server->getAppManager()->getAppVersion($appId, false);
697
+
698
+        self::setupBackgroundJobs($appData['background-jobs']);
699
+
700
+        //set remote/public handlers
701
+        if (array_key_exists('ocsid', $appData)) {
702
+            \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
703
+        } elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
704
+            \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
705
+        }
706
+        foreach ($appData['remote'] as $name => $path) {
707
+            \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
708
+        }
709
+        foreach ($appData['public'] as $name => $path) {
710
+            \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
711
+        }
712
+
713
+        self::setAppTypes($appId);
714
+
715
+        $version = Server::get(\OCP\App\IAppManager::class)->getAppVersion($appId);
716
+        \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
717
+
718
+        // migrate eventual new config keys in the process
719
+        /** @psalm-suppress InternalMethod */
720
+        Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId);
721
+
722
+        \OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
723
+        \OC::$server->get(IEventDispatcher::class)->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
724
+            ManagerEvent::EVENT_APP_UPDATE, $appId
725
+        ));
726
+
727
+        return true;
728
+    }
729
+
730
+    /**
731
+     * @param string $appId
732
+     * @param string[] $steps
733
+     * @throws \OC\NeedsUpdateException
734
+     */
735
+    public static function executeRepairSteps(string $appId, array $steps) {
736
+        if (empty($steps)) {
737
+            return;
738
+        }
739
+        // load the app
740
+        self::loadApp($appId);
741
+
742
+        $dispatcher = Server::get(IEventDispatcher::class);
743
+
744
+        // load the steps
745
+        $r = Server::get(Repair::class);
746
+        foreach ($steps as $step) {
747
+            try {
748
+                $r->addStep($step);
749
+            } catch (Exception $ex) {
750
+                $dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
751
+                logger('core')->error('Failed to add app migration step ' . $step, ['exception' => $ex]);
752
+            }
753
+        }
754
+        // run the steps
755
+        $r->run();
756
+    }
757
+
758
+    public static function setupBackgroundJobs(array $jobs) {
759
+        $queue = \OC::$server->getJobList();
760
+        foreach ($jobs as $job) {
761
+            $queue->add($job);
762
+        }
763
+    }
764
+
765
+    /**
766
+     * @param string $appId
767
+     * @param string[] $steps
768
+     */
769
+    private static function setupLiveMigrations(string $appId, array $steps) {
770
+        $queue = \OC::$server->getJobList();
771
+        foreach ($steps as $step) {
772
+            $queue->add('OC\Migration\BackgroundRepair', [
773
+                'app' => $appId,
774
+                'step' => $step]);
775
+        }
776
+    }
777
+
778
+    /**
779
+     * @param \OCP\IConfig $config
780
+     * @param \OCP\IL10N $l
781
+     * @param array $info
782
+     * @throws \Exception
783
+     */
784
+    public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
785
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
786
+        $missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
787
+        if (!empty($missing)) {
788
+            $missingMsg = implode(PHP_EOL, $missing);
789
+            throw new \Exception(
790
+                $l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
791
+                    [$info['name'], $missingMsg]
792
+                )
793
+            );
794
+        }
795
+    }
796 796
 }
Please login to merge, or discard this patch.
Spacing   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -101,7 +101,7 @@  discard block
 block discarded – undo
101 101
 	 * @param bool $force
102 102
 	 */
103 103
 	public static function registerAutoloading(string $app, string $path, bool $force = false) {
104
-		$key = $app . '-' . $path;
104
+		$key = $app.'-'.$path;
105 105
 		if (!$force && isset(self::$alreadyRegistered[$key])) {
106 106
 			return;
107 107
 		}
@@ -112,15 +112,15 @@  discard block
 block discarded – undo
112 112
 		$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
113 113
 		\OC::$server->registerNamespace($app, $appNamespace);
114 114
 
115
-		if (file_exists($path . '/composer/autoload.php')) {
116
-			require_once $path . '/composer/autoload.php';
115
+		if (file_exists($path.'/composer/autoload.php')) {
116
+			require_once $path.'/composer/autoload.php';
117 117
 		} else {
118
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
118
+			\OC::$composerAutoloader->addPsr4($appNamespace.'\\', $path.'/lib/', true);
119 119
 		}
120 120
 
121 121
 		// Register Test namespace only when testing
122 122
 		if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
123
-			\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
123
+			\OC::$composerAutoloader->addPsr4($appNamespace.'\\Tests\\', $path.'/tests/', true);
124 124
 		}
125 125
 	}
126 126
 
@@ -190,8 +190,8 @@  discard block
 block discarded – undo
190 190
 		} else {
191 191
 			$apps = $appManager->getEnabledAppsForUser($user);
192 192
 		}
193
-		$apps = array_filter($apps, function ($app) {
194
-			return $app !== 'files';//we add this manually
193
+		$apps = array_filter($apps, function($app) {
194
+			return $app !== 'files'; //we add this manually
195 195
 		});
196 196
 		sort($apps);
197 197
 		array_unshift($apps, 'files');
@@ -274,7 +274,7 @@  discard block
 block discarded – undo
274 274
 
275 275
 		$possibleApps = [];
276 276
 		foreach (OC::$APPSROOTS as $dir) {
277
-			if (file_exists($dir['path'] . '/' . $appId)) {
277
+			if (file_exists($dir['path'].'/'.$appId)) {
278 278
 				$possibleApps[] = $dir;
279 279
 			}
280 280
 		}
@@ -288,7 +288,7 @@  discard block
 block discarded – undo
288 288
 		} else {
289 289
 			$versionToLoad = [];
290 290
 			foreach ($possibleApps as $possibleApp) {
291
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
291
+				$version = self::getAppVersionByPath($possibleApp['path'].'/'.$appId);
292 292
 				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
293 293
 					$versionToLoad = [
294 294
 						'dir' => $possibleApp,
@@ -318,11 +318,11 @@  discard block
 block discarded – undo
318 318
 		if ($appId === '') {
319 319
 			return false;
320 320
 		} elseif ($appId === 'core') {
321
-			return __DIR__ . '/../../../core';
321
+			return __DIR__.'/../../../core';
322 322
 		}
323 323
 
324 324
 		if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
325
-			return $dir['path'] . '/' . $appId;
325
+			return $dir['path'].'/'.$appId;
326 326
 		}
327 327
 		return false;
328 328
 	}
@@ -337,7 +337,7 @@  discard block
 block discarded – undo
337 337
 	 */
338 338
 	public static function getAppWebPath(string $appId) {
339 339
 		if (($dir = self::findAppInDirectories($appId)) != false) {
340
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
340
+			return OC::$WEBROOT.$dir['url'].'/'.$appId;
341 341
 		}
342 342
 		return false;
343 343
 	}
@@ -349,7 +349,7 @@  discard block
 block discarded – undo
349 349
 	 * @return string
350 350
 	 */
351 351
 	public static function getAppVersionByPath(string $path): string {
352
-		$infoFile = $path . '/appinfo/info.xml';
352
+		$infoFile = $path.'/appinfo/info.xml';
353 353
 		$appData = Server::get(IAppManager::class)->getAppInfoByPath($infoFile);
354 354
 		return $appData['version'] ?? '';
355 355
 	}
@@ -487,12 +487,12 @@  discard block
 block discarded – undo
487 487
 			if (!in_array($app, $blacklist)) {
488 488
 				$info = $appManager->getAppInfo($app, false, $langCode);
489 489
 				if (!is_array($info)) {
490
-					Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
490
+					Server::get(LoggerInterface::class)->error('Could not read app info file for app "'.$app.'"', ['app' => 'core']);
491 491
 					continue;
492 492
 				}
493 493
 
494 494
 				if (!isset($info['name'])) {
495
-					Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
495
+					Server::get(LoggerInterface::class)->error('App id "'.$app.'" has no name in appinfo', ['app' => 'core']);
496 496
 					continue;
497 497
 				}
498 498
 
@@ -524,12 +524,12 @@  discard block
 block discarded – undo
524 524
 
525 525
 				$appPath = self::getAppPath($app);
526 526
 				if ($appPath !== false) {
527
-					$appIcon = $appPath . '/img/' . $app . '.svg';
527
+					$appIcon = $appPath.'/img/'.$app.'.svg';
528 528
 					if (file_exists($appIcon)) {
529
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
529
+						$info['preview'] = $urlGenerator->imagePath($app, $app.'.svg');
530 530
 						$info['previewAsIcon'] = true;
531 531
 					} else {
532
-						$appIcon = $appPath . '/img/app.svg';
532
+						$appIcon = $appPath.'/img/app.svg';
533 533
 						if (file_exists($appIcon)) {
534 534
 							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
535 535
 							$info['previewAsIcon'] = true;
@@ -665,8 +665,8 @@  discard block
 block discarded – undo
665 665
 			return false;
666 666
 		}
667 667
 
668
-		if (is_file($appPath . '/appinfo/database.xml')) {
669
-			Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
668
+		if (is_file($appPath.'/appinfo/database.xml')) {
669
+			Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in '.$appId);
670 670
 			return false;
671 671
 		}
672 672
 
@@ -704,10 +704,10 @@  discard block
 block discarded – undo
704 704
 			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
705 705
 		}
706 706
 		foreach ($appData['remote'] as $name => $path) {
707
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
707
+			\OC::$server->getConfig()->setAppValue('core', 'remote_'.$name, $appId.'/'.$path);
708 708
 		}
709 709
 		foreach ($appData['public'] as $name => $path) {
710
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
710
+			\OC::$server->getConfig()->setAppValue('core', 'public_'.$name, $appId.'/'.$path);
711 711
 		}
712 712
 
713 713
 		self::setAppTypes($appId);
@@ -748,7 +748,7 @@  discard block
 block discarded – undo
748 748
 				$r->addStep($step);
749 749
 			} catch (Exception $ex) {
750 750
 				$dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
751
-				logger('core')->error('Failed to add app migration step ' . $step, ['exception' => $ex]);
751
+				logger('core')->error('Failed to add app migration step '.$step, ['exception' => $ex]);
752 752
 			}
753 753
 		}
754 754
 		// run the steps
Please login to merge, or discard this patch.