Completed
Push — master ( 345a0a...c0b31d )
by
unknown
23:52 queued 14s
created
tests/lib/InstallerTest.php 2 patches
Indentation   +450 added lines, -450 removed lines patch added patch discarded remove patch
@@ -26,153 +26,153 @@  discard block
 block discarded – undo
26 26
  * @group DB
27 27
  */
28 28
 class InstallerTest extends TestCase {
29
-	private static $appid = 'testapp';
30
-	private $appstore;
31
-	/** @var AppFetcher|\PHPUnit\Framework\MockObject\MockObject */
32
-	private $appFetcher;
33
-	/** @var IClientService|\PHPUnit\Framework\MockObject\MockObject */
34
-	private $clientService;
35
-	/** @var ITempManager|\PHPUnit\Framework\MockObject\MockObject */
36
-	private $tempManager;
37
-	/** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
38
-	private $logger;
39
-	/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
40
-	private $config;
41
-
42
-	protected function setUp(): void {
43
-		parent::setUp();
44
-
45
-		$this->appFetcher = $this->createMock(AppFetcher::class);
46
-		$this->clientService = $this->createMock(IClientService::class);
47
-		$this->tempManager = $this->createMock(ITempManager::class);
48
-		$this->logger = $this->createMock(LoggerInterface::class);
49
-		$this->config = $this->createMock(IConfig::class);
50
-
51
-		$config = Server::get(IConfig::class);
52
-		$this->appstore = $config->setSystemValue('appstoreenabled', true);
53
-		$config->setSystemValue('appstoreenabled', true);
54
-		$installer = new Installer(
55
-			Server::get(AppFetcher::class),
56
-			Server::get(IClientService::class),
57
-			Server::get(ITempManager::class),
58
-			Server::get(LoggerInterface::class),
59
-			$config,
60
-			false
61
-		);
62
-		$installer->removeApp(self::$appid);
63
-	}
64
-
65
-	protected function getInstaller() {
66
-		return new Installer(
67
-			$this->appFetcher,
68
-			$this->clientService,
69
-			$this->tempManager,
70
-			$this->logger,
71
-			$this->config,
72
-			false
73
-		);
74
-	}
75
-
76
-	protected function tearDown(): void {
77
-		$installer = new Installer(
78
-			Server::get(AppFetcher::class),
79
-			Server::get(IClientService::class),
80
-			Server::get(ITempManager::class),
81
-			Server::get(LoggerInterface::class),
82
-			Server::get(IConfig::class),
83
-			false
84
-		);
85
-		$installer->removeApp(self::$appid);
86
-		Server::get(IConfig::class)->setSystemValue('appstoreenabled', $this->appstore);
87
-
88
-		parent::tearDown();
89
-	}
90
-
91
-	public function testInstallApp(): void {
92
-		// Read the current version of the app to check for bug #2572
93
-		Server::get(IAppManager::class)->getAppVersion('testapp', true);
94
-
95
-		// Build installer
96
-		$installer = new Installer(
97
-			Server::get(AppFetcher::class),
98
-			Server::get(IClientService::class),
99
-			Server::get(ITempManager::class),
100
-			Server::get(LoggerInterface::class),
101
-			Server::get(IConfig::class),
102
-			false
103
-		);
104
-
105
-		// Extract app
106
-		$pathOfTestApp = __DIR__ . '/../data/testapp.zip';
107
-		$tar = new ZIP($pathOfTestApp);
108
-		$tar->extract($installer->getInstallPath());
109
-
110
-		// Install app
111
-		$this->assertNull(Server::get(IConfig::class)->getAppValue('testapp', 'enabled', null), 'Check that the app is not listed before installation');
112
-		$this->assertSame('testapp', $installer->installApp(self::$appid));
113
-		$this->assertSame('no', Server::get(IConfig::class)->getAppValue('testapp', 'enabled', null), 'Check that the app is listed after installation');
114
-		$this->assertSame('0.9', Server::get(IConfig::class)->getAppValue('testapp', 'installed_version'));
115
-		$installer->removeApp(self::$appid);
116
-	}
117
-
118
-	public static function updateArrayProvider(): array {
119
-		return [
120
-			// Update available
121
-			[
122
-				[
123
-					[
124
-						'id' => 'files',
125
-						'releases' => [
126
-							[
127
-								'version' => '1111.0'
128
-							],
129
-						],
130
-					],
131
-				],
132
-				'1111.0',
133
-			],
134
-			// No update available
135
-			[
136
-				[
137
-					[
138
-						'id' => 'files',
139
-						'releases' => [
140
-							[
141
-								'version' => '1.0'
142
-							],
143
-						],
144
-					],
145
-				],
146
-				false,
147
-			],
148
-		];
149
-	}
150
-
151
-	/**
152
-	 * @param array $appArray
153
-	 * @param string|bool $updateAvailable
154
-	 */
155
-	#[\PHPUnit\Framework\Attributes\DataProvider('updateArrayProvider')]
156
-	public function testIsUpdateAvailable(array $appArray, $updateAvailable): void {
157
-		$this->appFetcher
158
-			->expects($this->once())
159
-			->method('get')
160
-			->willReturn($appArray);
161
-
162
-		$installer = $this->getInstaller();
163
-		$this->assertSame($updateAvailable, $installer->isUpdateAvailable('files'));
164
-		$this->assertSame($updateAvailable, $installer->isUpdateAvailable('files'), 'Cached result should be returned and fetcher should be only called once');
165
-	}
166
-
167
-
168
-	public function testDownloadAppWithRevokedCertificate(): void {
169
-		$this->expectException(\Exception::class);
170
-		$this->expectExceptionMessage('Certificate "4112" has been revoked');
171
-
172
-		$appArray = [
173
-			[
174
-				'id' => 'news',
175
-				'certificate' => '-----BEGIN CERTIFICATE-----
29
+    private static $appid = 'testapp';
30
+    private $appstore;
31
+    /** @var AppFetcher|\PHPUnit\Framework\MockObject\MockObject */
32
+    private $appFetcher;
33
+    /** @var IClientService|\PHPUnit\Framework\MockObject\MockObject */
34
+    private $clientService;
35
+    /** @var ITempManager|\PHPUnit\Framework\MockObject\MockObject */
36
+    private $tempManager;
37
+    /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
38
+    private $logger;
39
+    /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
40
+    private $config;
41
+
42
+    protected function setUp(): void {
43
+        parent::setUp();
44
+
45
+        $this->appFetcher = $this->createMock(AppFetcher::class);
46
+        $this->clientService = $this->createMock(IClientService::class);
47
+        $this->tempManager = $this->createMock(ITempManager::class);
48
+        $this->logger = $this->createMock(LoggerInterface::class);
49
+        $this->config = $this->createMock(IConfig::class);
50
+
51
+        $config = Server::get(IConfig::class);
52
+        $this->appstore = $config->setSystemValue('appstoreenabled', true);
53
+        $config->setSystemValue('appstoreenabled', true);
54
+        $installer = new Installer(
55
+            Server::get(AppFetcher::class),
56
+            Server::get(IClientService::class),
57
+            Server::get(ITempManager::class),
58
+            Server::get(LoggerInterface::class),
59
+            $config,
60
+            false
61
+        );
62
+        $installer->removeApp(self::$appid);
63
+    }
64
+
65
+    protected function getInstaller() {
66
+        return new Installer(
67
+            $this->appFetcher,
68
+            $this->clientService,
69
+            $this->tempManager,
70
+            $this->logger,
71
+            $this->config,
72
+            false
73
+        );
74
+    }
75
+
76
+    protected function tearDown(): void {
77
+        $installer = new Installer(
78
+            Server::get(AppFetcher::class),
79
+            Server::get(IClientService::class),
80
+            Server::get(ITempManager::class),
81
+            Server::get(LoggerInterface::class),
82
+            Server::get(IConfig::class),
83
+            false
84
+        );
85
+        $installer->removeApp(self::$appid);
86
+        Server::get(IConfig::class)->setSystemValue('appstoreenabled', $this->appstore);
87
+
88
+        parent::tearDown();
89
+    }
90
+
91
+    public function testInstallApp(): void {
92
+        // Read the current version of the app to check for bug #2572
93
+        Server::get(IAppManager::class)->getAppVersion('testapp', true);
94
+
95
+        // Build installer
96
+        $installer = new Installer(
97
+            Server::get(AppFetcher::class),
98
+            Server::get(IClientService::class),
99
+            Server::get(ITempManager::class),
100
+            Server::get(LoggerInterface::class),
101
+            Server::get(IConfig::class),
102
+            false
103
+        );
104
+
105
+        // Extract app
106
+        $pathOfTestApp = __DIR__ . '/../data/testapp.zip';
107
+        $tar = new ZIP($pathOfTestApp);
108
+        $tar->extract($installer->getInstallPath());
109
+
110
+        // Install app
111
+        $this->assertNull(Server::get(IConfig::class)->getAppValue('testapp', 'enabled', null), 'Check that the app is not listed before installation');
112
+        $this->assertSame('testapp', $installer->installApp(self::$appid));
113
+        $this->assertSame('no', Server::get(IConfig::class)->getAppValue('testapp', 'enabled', null), 'Check that the app is listed after installation');
114
+        $this->assertSame('0.9', Server::get(IConfig::class)->getAppValue('testapp', 'installed_version'));
115
+        $installer->removeApp(self::$appid);
116
+    }
117
+
118
+    public static function updateArrayProvider(): array {
119
+        return [
120
+            // Update available
121
+            [
122
+                [
123
+                    [
124
+                        'id' => 'files',
125
+                        'releases' => [
126
+                            [
127
+                                'version' => '1111.0'
128
+                            ],
129
+                        ],
130
+                    ],
131
+                ],
132
+                '1111.0',
133
+            ],
134
+            // No update available
135
+            [
136
+                [
137
+                    [
138
+                        'id' => 'files',
139
+                        'releases' => [
140
+                            [
141
+                                'version' => '1.0'
142
+                            ],
143
+                        ],
144
+                    ],
145
+                ],
146
+                false,
147
+            ],
148
+        ];
149
+    }
150
+
151
+    /**
152
+     * @param array $appArray
153
+     * @param string|bool $updateAvailable
154
+     */
155
+    #[\PHPUnit\Framework\Attributes\DataProvider('updateArrayProvider')]
156
+    public function testIsUpdateAvailable(array $appArray, $updateAvailable): void {
157
+        $this->appFetcher
158
+            ->expects($this->once())
159
+            ->method('get')
160
+            ->willReturn($appArray);
161
+
162
+        $installer = $this->getInstaller();
163
+        $this->assertSame($updateAvailable, $installer->isUpdateAvailable('files'));
164
+        $this->assertSame($updateAvailable, $installer->isUpdateAvailable('files'), 'Cached result should be returned and fetcher should be only called once');
165
+    }
166
+
167
+
168
+    public function testDownloadAppWithRevokedCertificate(): void {
169
+        $this->expectException(\Exception::class);
170
+        $this->expectExceptionMessage('Certificate "4112" has been revoked');
171
+
172
+        $appArray = [
173
+            [
174
+                'id' => 'news',
175
+                'certificate' => '-----BEGIN CERTIFICATE-----
176 176
 MIIEAjCCAuoCAhAQMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
177 177
 VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
178 178
 MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
@@ -196,27 +196,27 @@  discard block
 block discarded – undo
196 196
 tczQMzLtVdTg5z8XMi//6TkAPxRPjYi8Vef/s2mLo7KystTmofxI/HZePSieJ9tj
197 197
 gLgK8d8sKL60JMmKHN3boHrsThKBVA==
198 198
 -----END CERTIFICATE-----',
199
-			],
200
-		];
201
-		$this->appFetcher
202
-			->expects($this->once())
203
-			->method('get')
204
-			->willReturn($appArray);
199
+            ],
200
+        ];
201
+        $this->appFetcher
202
+            ->expects($this->once())
203
+            ->method('get')
204
+            ->willReturn($appArray);
205 205
 
206 206
 
207
-		$installer = $this->getInstaller();
208
-		$installer->downloadApp('news');
209
-	}
207
+        $installer = $this->getInstaller();
208
+        $installer->downloadApp('news');
209
+    }
210 210
 
211 211
 
212
-	public function testDownloadAppWithNotNextcloudCertificate(): void {
213
-		$this->expectException(\Exception::class);
214
-		$this->expectExceptionMessage('App with id news has a certificate not issued by a trusted Code Signing Authority');
212
+    public function testDownloadAppWithNotNextcloudCertificate(): void {
213
+        $this->expectException(\Exception::class);
214
+        $this->expectExceptionMessage('App with id news has a certificate not issued by a trusted Code Signing Authority');
215 215
 
216
-		$appArray = [
217
-			[
218
-				'id' => 'news',
219
-				'certificate' => '-----BEGIN CERTIFICATE-----
216
+        $appArray = [
217
+            [
218
+                'id' => 'news',
219
+                'certificate' => '-----BEGIN CERTIFICATE-----
220 220
 MIID8TCCAdkCAhAAMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNVBAYTAlVTMQ8wDQYD
221 221
 VQQIDAZCb3N0b24xFjAUBgNVBAoMDW93bkNsb3VkIEluYy4xNTAzBgNVBAMMLG93
222 222
 bkNsb3VkIENvZGUgU2lnbmluZyBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MB4XDTE2
@@ -240,26 +240,26 @@  discard block
 block discarded – undo
240 240
 crl5lBlKjXh2GP0+omSO3x1jX4+iQPCW2TWoyKkUdLu/hGHG2w8RrTeme+kATECH
241 241
 YSu356M=
242 242
 -----END CERTIFICATE-----',
243
-			],
244
-		];
245
-		$this->appFetcher
246
-			->expects($this->once())
247
-			->method('get')
248
-			->willReturn($appArray);
249
-
250
-		$installer = $this->getInstaller();
251
-		$installer->downloadApp('news');
252
-	}
253
-
254
-
255
-	public function testDownloadAppWithDifferentCN(): void {
256
-		$this->expectException(\Exception::class);
257
-		$this->expectExceptionMessage('App with id news has a cert issued to passman');
258
-
259
-		$appArray = [
260
-			[
261
-				'id' => 'news',
262
-				'certificate' => '-----BEGIN CERTIFICATE-----
243
+            ],
244
+        ];
245
+        $this->appFetcher
246
+            ->expects($this->once())
247
+            ->method('get')
248
+            ->willReturn($appArray);
249
+
250
+        $installer = $this->getInstaller();
251
+        $installer->downloadApp('news');
252
+    }
253
+
254
+
255
+    public function testDownloadAppWithDifferentCN(): void {
256
+        $this->expectException(\Exception::class);
257
+        $this->expectExceptionMessage('App with id news has a cert issued to passman');
258
+
259
+        $appArray = [
260
+            [
261
+                'id' => 'news',
262
+                'certificate' => '-----BEGIN CERTIFICATE-----
263 263
 MIIEAjCCAuoCAhDUMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
264 264
 VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
265 265
 MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
@@ -283,26 +283,26 @@  discard block
 block discarded – undo
283 283
 u/spPSSVhaun5BA1FlphB2TkgnzlCmxJa63nFY045e/Jq+IKMcqqZl/092gbI2EQ
284 284
 5EpZaQ1l6H5DBXwrz58a8WTPC2Mu8g==
285 285
 -----END CERTIFICATE-----',
286
-			],
287
-		];
288
-		$this->appFetcher
289
-			->expects($this->once())
290
-			->method('get')
291
-			->willReturn($appArray);
292
-
293
-		$installer = $this->getInstaller();
294
-		$installer->downloadApp('news');
295
-	}
296
-
297
-
298
-	public function testDownloadAppWithInvalidSignature(): void {
299
-		$this->expectException(\Exception::class);
300
-		$this->expectExceptionMessage('App with id passman has invalid signature');
301
-
302
-		$appArray = [
303
-			[
304
-				'id' => 'passman',
305
-				'certificate' => '-----BEGIN CERTIFICATE-----
286
+            ],
287
+        ];
288
+        $this->appFetcher
289
+            ->expects($this->once())
290
+            ->method('get')
291
+            ->willReturn($appArray);
292
+
293
+        $installer = $this->getInstaller();
294
+        $installer->downloadApp('news');
295
+    }
296
+
297
+
298
+    public function testDownloadAppWithInvalidSignature(): void {
299
+        $this->expectException(\Exception::class);
300
+        $this->expectExceptionMessage('App with id passman has invalid signature');
301
+
302
+        $appArray = [
303
+            [
304
+                'id' => 'passman',
305
+                'certificate' => '-----BEGIN CERTIFICATE-----
306 306
 MIIEAjCCAuoCAhDUMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
307 307
 VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
308 308
 MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
@@ -326,51 +326,51 @@  discard block
 block discarded – undo
326 326
 u/spPSSVhaun5BA1FlphB2TkgnzlCmxJa63nFY045e/Jq+IKMcqqZl/092gbI2EQ
327 327
 5EpZaQ1l6H5DBXwrz58a8WTPC2Mu8g==
328 328
 -----END CERTIFICATE-----',
329
-				'releases' => [
330
-					[
331
-						'download' => 'https://example.com',
332
-						'signature' => 'MySignature',
333
-					],
334
-					[
335
-						'download' => 'https://nextcloud.com',
336
-					],
337
-				],
338
-			],
339
-		];
340
-		$this->appFetcher
341
-			->expects($this->once())
342
-			->method('get')
343
-			->willReturn($appArray);
344
-		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
345
-		copy(__DIR__ . '/../data/testapp.tar.gz', $realTmpFile);
346
-		$this->tempManager
347
-			->expects($this->once())
348
-			->method('getTemporaryFile')
349
-			->with('.tar.gz')
350
-			->willReturn($realTmpFile);
351
-		$client = $this->createMock(IClient::class);
352
-		$client
353
-			->expects($this->once())
354
-			->method('get')
355
-			->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
356
-		$this->clientService
357
-			->expects($this->once())
358
-			->method('newClient')
359
-			->willReturn($client);
360
-
361
-		$installer = $this->getInstaller();
362
-		$installer->downloadApp('passman');
363
-	}
364
-
365
-
366
-	public function testDownloadAppWithMoreThanOneFolderDownloaded(): void {
367
-		$this->expectException(\Exception::class);
368
-		$this->expectExceptionMessage('Extracted app testapp has more than 1 folder');
369
-
370
-		$appArray = [
371
-			[
372
-				'id' => 'testapp',
373
-				'certificate' => '-----BEGIN CERTIFICATE-----
329
+                'releases' => [
330
+                    [
331
+                        'download' => 'https://example.com',
332
+                        'signature' => 'MySignature',
333
+                    ],
334
+                    [
335
+                        'download' => 'https://nextcloud.com',
336
+                    ],
337
+                ],
338
+            ],
339
+        ];
340
+        $this->appFetcher
341
+            ->expects($this->once())
342
+            ->method('get')
343
+            ->willReturn($appArray);
344
+        $realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
345
+        copy(__DIR__ . '/../data/testapp.tar.gz', $realTmpFile);
346
+        $this->tempManager
347
+            ->expects($this->once())
348
+            ->method('getTemporaryFile')
349
+            ->with('.tar.gz')
350
+            ->willReturn($realTmpFile);
351
+        $client = $this->createMock(IClient::class);
352
+        $client
353
+            ->expects($this->once())
354
+            ->method('get')
355
+            ->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
356
+        $this->clientService
357
+            ->expects($this->once())
358
+            ->method('newClient')
359
+            ->willReturn($client);
360
+
361
+        $installer = $this->getInstaller();
362
+        $installer->downloadApp('passman');
363
+    }
364
+
365
+
366
+    public function testDownloadAppWithMoreThanOneFolderDownloaded(): void {
367
+        $this->expectException(\Exception::class);
368
+        $this->expectExceptionMessage('Extracted app testapp has more than 1 folder');
369
+
370
+        $appArray = [
371
+            [
372
+                'id' => 'testapp',
373
+                'certificate' => '-----BEGIN CERTIFICATE-----
374 374
 MIIEAjCCAuoCAhAbMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
375 375
 VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
376 376
 MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
@@ -394,10 +394,10 @@  discard block
 block discarded – undo
394 394
 Zh8Xg8UMNrOtXc1Wx1Wmjaa4ZE9dY6/KkU2ny2UWyDHKU/9VE8QQ4HN93gxU4+H7
395 395
 cUg0V1uAxqUvKytKkMfcyPWsz/AINA==
396 396
 -----END CERTIFICATE-----',
397
-				'releases' => [
398
-					[
399
-						'download' => 'https://example.com',
400
-						'signature' => 'h8H3tUy2dDlwrV/hY/ZxqYqe8Vue+IINluLtAt1HxX2cjz3vdoVHJRINRkMYYcdz
397
+                'releases' => [
398
+                    [
399
+                        'download' => 'https://example.com',
400
+                        'signature' => 'h8H3tUy2dDlwrV/hY/ZxqYqe8Vue+IINluLtAt1HxX2cjz3vdoVHJRINRkMYYcdz
401 401
 VlndvHyKdqJHDAACphR8tVV6EFrPermn7gEgWk7a51LbUM7sAN7RV7ijEooUo+TQ
402 402
 jNW9Ch48Wg3jvebMwWNr5t5U4MEXTP5f0YX/kxvkJoUrG3a3spt7ziEuHaq8IPvt
403 403
 Jj/JSDFhvRNpom7yNNcI1Ijoq8yC11sg7RJBNfrHdGPHPZVz2SyBiY9OcvgGSpUU
@@ -408,53 +408,53 @@  discard block
 block discarded – undo
408 408
 GqEhdDmOHsxNaeJ08Hlptq5yLv3+0wEdtriVjgAZNVduHG1F1FkhPIrDHaB6pd67
409 409
 0AFvO/pZgMSHDRHD+safBgaLb5dBZ895Qvudbq3RQevVnO+YZQYZkpmjoF/+TQ7/
410 410
 YwDVP+QmNRzx72jtqAN/Kc3CvQ9nkgYhU65B95aX0xA=',
411
-					],
412
-					[
413
-						'download' => 'https://nextcloud.com',
414
-					],
415
-				],
416
-			],
417
-		];
418
-		$this->appFetcher
419
-			->expects($this->once())
420
-			->method('get')
421
-			->willReturn($appArray);
422
-		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
423
-		copy(__DIR__ . '/../data/testapp1.tar.gz', $realTmpFile);
424
-		$this->tempManager
425
-			->expects($this->once())
426
-			->method('getTemporaryFile')
427
-			->with('.tar.gz')
428
-			->willReturn($realTmpFile);
429
-		$realTmpFolder = Server::get(ITempManager::class)->getTemporaryFolder();
430
-		mkdir($realTmpFolder . '/testfolder');
431
-		$this->tempManager
432
-			->expects($this->once())
433
-			->method('getTemporaryFolder')
434
-			->willReturn($realTmpFolder);
435
-		$client = $this->createMock(IClient::class);
436
-		$client
437
-			->expects($this->once())
438
-			->method('get')
439
-			->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
440
-		$this->clientService
441
-			->expects($this->once())
442
-			->method('newClient')
443
-			->willReturn($client);
444
-
445
-		$installer = $this->getInstaller();
446
-		$installer->downloadApp('testapp');
447
-	}
448
-
449
-
450
-	public function testDownloadAppWithMismatchingIdentifier(): void {
451
-		$this->expectException(\Exception::class);
452
-		$this->expectExceptionMessage('App for id testapp has a wrong app ID in info.xml: testapp1');
453
-
454
-		$appArray = [
455
-			[
456
-				'id' => 'testapp',
457
-				'certificate' => '-----BEGIN CERTIFICATE-----
411
+                    ],
412
+                    [
413
+                        'download' => 'https://nextcloud.com',
414
+                    ],
415
+                ],
416
+            ],
417
+        ];
418
+        $this->appFetcher
419
+            ->expects($this->once())
420
+            ->method('get')
421
+            ->willReturn($appArray);
422
+        $realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
423
+        copy(__DIR__ . '/../data/testapp1.tar.gz', $realTmpFile);
424
+        $this->tempManager
425
+            ->expects($this->once())
426
+            ->method('getTemporaryFile')
427
+            ->with('.tar.gz')
428
+            ->willReturn($realTmpFile);
429
+        $realTmpFolder = Server::get(ITempManager::class)->getTemporaryFolder();
430
+        mkdir($realTmpFolder . '/testfolder');
431
+        $this->tempManager
432
+            ->expects($this->once())
433
+            ->method('getTemporaryFolder')
434
+            ->willReturn($realTmpFolder);
435
+        $client = $this->createMock(IClient::class);
436
+        $client
437
+            ->expects($this->once())
438
+            ->method('get')
439
+            ->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
440
+        $this->clientService
441
+            ->expects($this->once())
442
+            ->method('newClient')
443
+            ->willReturn($client);
444
+
445
+        $installer = $this->getInstaller();
446
+        $installer->downloadApp('testapp');
447
+    }
448
+
449
+
450
+    public function testDownloadAppWithMismatchingIdentifier(): void {
451
+        $this->expectException(\Exception::class);
452
+        $this->expectExceptionMessage('App for id testapp has a wrong app ID in info.xml: testapp1');
453
+
454
+        $appArray = [
455
+            [
456
+                'id' => 'testapp',
457
+                'certificate' => '-----BEGIN CERTIFICATE-----
458 458
 MIIEAjCCAuoCAhAbMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
459 459
 VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
460 460
 MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
@@ -478,10 +478,10 @@  discard block
 block discarded – undo
478 478
 Zh8Xg8UMNrOtXc1Wx1Wmjaa4ZE9dY6/KkU2ny2UWyDHKU/9VE8QQ4HN93gxU4+H7
479 479
 cUg0V1uAxqUvKytKkMfcyPWsz/AINA==
480 480
 -----END CERTIFICATE-----',
481
-				'releases' => [
482
-					[
483
-						'download' => 'https://example.com',
484
-						'signature' => 'h8H3tUy2dDlwrV/hY/ZxqYqe8Vue+IINluLtAt1HxX2cjz3vdoVHJRINRkMYYcdz
481
+                'releases' => [
482
+                    [
483
+                        'download' => 'https://example.com',
484
+                        'signature' => 'h8H3tUy2dDlwrV/hY/ZxqYqe8Vue+IINluLtAt1HxX2cjz3vdoVHJRINRkMYYcdz
485 485
 VlndvHyKdqJHDAACphR8tVV6EFrPermn7gEgWk7a51LbUM7sAN7RV7ijEooUo+TQ
486 486
 jNW9Ch48Wg3jvebMwWNr5t5U4MEXTP5f0YX/kxvkJoUrG3a3spt7ziEuHaq8IPvt
487 487
 Jj/JSDFhvRNpom7yNNcI1Ijoq8yC11sg7RJBNfrHdGPHPZVz2SyBiY9OcvgGSpUU
@@ -492,48 +492,48 @@  discard block
 block discarded – undo
492 492
 GqEhdDmOHsxNaeJ08Hlptq5yLv3+0wEdtriVjgAZNVduHG1F1FkhPIrDHaB6pd67
493 493
 0AFvO/pZgMSHDRHD+safBgaLb5dBZ895Qvudbq3RQevVnO+YZQYZkpmjoF/+TQ7/
494 494
 YwDVP+QmNRzx72jtqAN/Kc3CvQ9nkgYhU65B95aX0xA=',
495
-					],
496
-					[
497
-						'download' => 'https://nextcloud.com',
498
-					],
499
-				],
500
-			],
501
-		];
502
-		$this->appFetcher
503
-			->expects($this->once())
504
-			->method('get')
505
-			->willReturn($appArray);
506
-		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
507
-		copy(__DIR__ . '/../data/testapp1.tar.gz', $realTmpFile);
508
-		$this->tempManager
509
-			->expects($this->once())
510
-			->method('getTemporaryFile')
511
-			->with('.tar.gz')
512
-			->willReturn($realTmpFile);
513
-		$realTmpFolder = Server::get(ITempManager::class)->getTemporaryFolder();
514
-		$this->tempManager
515
-			->expects($this->once())
516
-			->method('getTemporaryFolder')
517
-			->willReturn($realTmpFolder);
518
-		$client = $this->createMock(IClient::class);
519
-		$client
520
-			->expects($this->once())
521
-			->method('get')
522
-			->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
523
-		$this->clientService
524
-			->expects($this->once())
525
-			->method('newClient')
526
-			->willReturn($client);
527
-
528
-		$installer = $this->getInstaller();
529
-		$installer->downloadApp('testapp');
530
-	}
531
-
532
-	public function testDownloadAppSuccessful(): void {
533
-		$appArray = [
534
-			[
535
-				'id' => 'testapp',
536
-				'certificate' => '-----BEGIN CERTIFICATE-----
495
+                    ],
496
+                    [
497
+                        'download' => 'https://nextcloud.com',
498
+                    ],
499
+                ],
500
+            ],
501
+        ];
502
+        $this->appFetcher
503
+            ->expects($this->once())
504
+            ->method('get')
505
+            ->willReturn($appArray);
506
+        $realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
507
+        copy(__DIR__ . '/../data/testapp1.tar.gz', $realTmpFile);
508
+        $this->tempManager
509
+            ->expects($this->once())
510
+            ->method('getTemporaryFile')
511
+            ->with('.tar.gz')
512
+            ->willReturn($realTmpFile);
513
+        $realTmpFolder = Server::get(ITempManager::class)->getTemporaryFolder();
514
+        $this->tempManager
515
+            ->expects($this->once())
516
+            ->method('getTemporaryFolder')
517
+            ->willReturn($realTmpFolder);
518
+        $client = $this->createMock(IClient::class);
519
+        $client
520
+            ->expects($this->once())
521
+            ->method('get')
522
+            ->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
523
+        $this->clientService
524
+            ->expects($this->once())
525
+            ->method('newClient')
526
+            ->willReturn($client);
527
+
528
+        $installer = $this->getInstaller();
529
+        $installer->downloadApp('testapp');
530
+    }
531
+
532
+    public function testDownloadAppSuccessful(): void {
533
+        $appArray = [
534
+            [
535
+                'id' => 'testapp',
536
+                'certificate' => '-----BEGIN CERTIFICATE-----
537 537
 MIIEAjCCAuoCAhAbMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
538 538
 VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
539 539
 MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
@@ -557,10 +557,10 @@  discard block
 block discarded – undo
557 557
 Zh8Xg8UMNrOtXc1Wx1Wmjaa4ZE9dY6/KkU2ny2UWyDHKU/9VE8QQ4HN93gxU4+H7
558 558
 cUg0V1uAxqUvKytKkMfcyPWsz/AINA==
559 559
 -----END CERTIFICATE-----',
560
-				'releases' => [
561
-					[
562
-						'download' => 'https://example.com',
563
-						'signature' => 'O5UWFRnSx4mSdEX83Uh9u7KW+Gl1OWU4uaFg6aYY19zc+lWP4rKCbAUH7Jo1Bohf
560
+                'releases' => [
561
+                    [
562
+                        'download' => 'https://example.com',
563
+                        'signature' => 'O5UWFRnSx4mSdEX83Uh9u7KW+Gl1OWU4uaFg6aYY19zc+lWP4rKCbAUH7Jo1Bohf
564 564
 qxQbhXs4cMqGmoL8dW4zeFUqSJCRk52LA+ciLezjPFv275q+BxEgyWOylLnbhBaz
565 565
 +v6lXLaeG0J/ry8wEdg+rwP8FCYPsvKlXSVbFjgubvCR/owKJJf5iL0B93noBwBN
566 566
 jfbcxi7Kh16HAKy6f/gVZ6hf/4Uo7iEFMCPEHjidope+ejUpqbd8XhQg5/yh7TQ7
@@ -571,63 +571,63 @@  discard block
 block discarded – undo
571 571
 7yQWrsV7QvAzygAOFsC0TlSNJbmMCljouUk9di4CUZ+xsQ6n6TZtE7gsdljlKjPS
572 572
 3Ys+e3V1HUaVzv8SaSmKwjRoQxQxHWLtXpJS2Yq+i+gq7LuC+aStzxAzV/h2plDW
573 573
 358picx/PobNDi71Q97+/CAOq+4wDOwhKwls7lwudIs=',
574
-					],
575
-					[
576
-						'download' => 'https://nextcloud.com',
577
-					],
578
-				],
579
-			],
580
-		];
581
-		$this->appFetcher
582
-			->expects($this->once())
583
-			->method('get')
584
-			->willReturn($appArray);
585
-		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
586
-		copy(__DIR__ . '/../data/testapp.tar.gz', $realTmpFile);
587
-		$this->tempManager
588
-			->expects($this->once())
589
-			->method('getTemporaryFile')
590
-			->with('.tar.gz')
591
-			->willReturn($realTmpFile);
592
-		$realTmpFolder = Server::get(ITempManager::class)->getTemporaryFolder();
593
-		$this->tempManager
594
-			->expects($this->once())
595
-			->method('getTemporaryFolder')
596
-			->willReturn($realTmpFolder);
597
-		$client = $this->createMock(IClient::class);
598
-		$client
599
-			->expects($this->once())
600
-			->method('get')
601
-			->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
602
-		$this->clientService
603
-			->expects($this->once())
604
-			->method('newClient')
605
-			->willReturn($client);
606
-
607
-		$installer = $this->getInstaller();
608
-		$installer->downloadApp('testapp');
609
-
610
-		$this->assertTrue(file_exists(__DIR__ . '/../../apps/testapp/appinfo/info.xml'));
611
-		$this->assertEquals('0.9', \OC_App::getAppVersionByPath(__DIR__ . '/../../apps/testapp/'));
612
-	}
613
-
614
-
615
-	public function testDownloadAppWithDowngrade(): void {
616
-		// Use previous test to download the application in version 0.9
617
-		$this->testDownloadAppSuccessful();
618
-
619
-		// Reset mocks
620
-		$this->appFetcher = $this->createMock(AppFetcher::class);
621
-		$this->clientService = $this->createMock(IClientService::class);
622
-		$this->tempManager = $this->createMock(ITempManager::class);
623
-
624
-		$this->expectException(\Exception::class);
625
-		$this->expectExceptionMessage('App for id testapp has version 0.9 and tried to update to lower version 0.8');
626
-
627
-		$appArray = [
628
-			[
629
-				'id' => 'testapp',
630
-				'certificate' => '-----BEGIN CERTIFICATE-----
574
+                    ],
575
+                    [
576
+                        'download' => 'https://nextcloud.com',
577
+                    ],
578
+                ],
579
+            ],
580
+        ];
581
+        $this->appFetcher
582
+            ->expects($this->once())
583
+            ->method('get')
584
+            ->willReturn($appArray);
585
+        $realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
586
+        copy(__DIR__ . '/../data/testapp.tar.gz', $realTmpFile);
587
+        $this->tempManager
588
+            ->expects($this->once())
589
+            ->method('getTemporaryFile')
590
+            ->with('.tar.gz')
591
+            ->willReturn($realTmpFile);
592
+        $realTmpFolder = Server::get(ITempManager::class)->getTemporaryFolder();
593
+        $this->tempManager
594
+            ->expects($this->once())
595
+            ->method('getTemporaryFolder')
596
+            ->willReturn($realTmpFolder);
597
+        $client = $this->createMock(IClient::class);
598
+        $client
599
+            ->expects($this->once())
600
+            ->method('get')
601
+            ->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
602
+        $this->clientService
603
+            ->expects($this->once())
604
+            ->method('newClient')
605
+            ->willReturn($client);
606
+
607
+        $installer = $this->getInstaller();
608
+        $installer->downloadApp('testapp');
609
+
610
+        $this->assertTrue(file_exists(__DIR__ . '/../../apps/testapp/appinfo/info.xml'));
611
+        $this->assertEquals('0.9', \OC_App::getAppVersionByPath(__DIR__ . '/../../apps/testapp/'));
612
+    }
613
+
614
+
615
+    public function testDownloadAppWithDowngrade(): void {
616
+        // Use previous test to download the application in version 0.9
617
+        $this->testDownloadAppSuccessful();
618
+
619
+        // Reset mocks
620
+        $this->appFetcher = $this->createMock(AppFetcher::class);
621
+        $this->clientService = $this->createMock(IClientService::class);
622
+        $this->tempManager = $this->createMock(ITempManager::class);
623
+
624
+        $this->expectException(\Exception::class);
625
+        $this->expectExceptionMessage('App for id testapp has version 0.9 and tried to update to lower version 0.8');
626
+
627
+        $appArray = [
628
+            [
629
+                'id' => 'testapp',
630
+                'certificate' => '-----BEGIN CERTIFICATE-----
631 631
 MIIEAjCCAuoCAhAbMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
632 632
 VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
633 633
 MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
@@ -651,10 +651,10 @@  discard block
 block discarded – undo
651 651
 Zh8Xg8UMNrOtXc1Wx1Wmjaa4ZE9dY6/KkU2ny2UWyDHKU/9VE8QQ4HN93gxU4+H7
652 652
 cUg0V1uAxqUvKytKkMfcyPWsz/AINA==
653 653
 -----END CERTIFICATE-----',
654
-				'releases' => [
655
-					[
656
-						'download' => 'https://example.com',
657
-						'signature' => 'KMSao4cKdMIYxeT8Bm4lrmSeIQnk7YzJZh+Vz+4LVSBwF+OMmcujryQuWLXmbPfg
654
+                'releases' => [
655
+                    [
656
+                        'download' => 'https://example.com',
657
+                        'signature' => 'KMSao4cKdMIYxeT8Bm4lrmSeIQnk7YzJZh+Vz+4LVSBwF+OMmcujryQuWLXmbPfg
658 658
 4hGI9zS025469VNjUoCprn01H8NBq3O1cXz+ewG1oxYWMMQFZDkOtUQ+XZ27b91t
659 659
 y0l45H6C8j0sTeSrUb/LCjrdm+buUygkhC2RZxCI6tLi4rYWj0MiqDz98XkbB3te
660 660
 pW3ZND6mG6Jxn1fnd35paqZ/+URMftoLQ4K+6vJoBVGnug9nk1RpGLouICI0zCrz
@@ -665,44 +665,44 @@  discard block
 block discarded – undo
665 665
 9WfeKJ/mavrSLVa7QqZ4RCcMigmijT1kdqbaEh05IZNrzs6VDcS2EIrbDX8SGXUk
666 666
 uDDkPXZEXqNDEjyONfDXVRLiqDa52Gg+I4vW/l/4ZOFgAWdZkqPPuZFaqzZpsJXm
667 667
 JXhrdaWDZ8fzpUjugrtC3qslsqL0dzgU37anS3HwrT8=',
668
-					],
669
-					[
670
-						'download' => 'https://nextcloud.com',
671
-					],
672
-				],
673
-			],
674
-		];
675
-		$this->appFetcher
676
-			->expects($this->once())
677
-			->method('get')
678
-			->willReturn($appArray);
679
-		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
680
-		copy(__DIR__ . '/../data/testapp.0.8.tar.gz', $realTmpFile);
681
-		$this->tempManager
682
-			->expects($this->once())
683
-			->method('getTemporaryFile')
684
-			->with('.tar.gz')
685
-			->willReturn($realTmpFile);
686
-		$realTmpFolder = Server::get(ITempManager::class)->getTemporaryFolder();
687
-		$this->tempManager
688
-			->expects($this->once())
689
-			->method('getTemporaryFolder')
690
-			->willReturn($realTmpFolder);
691
-		$client = $this->createMock(IClient::class);
692
-		$client
693
-			->expects($this->once())
694
-			->method('get')
695
-			->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
696
-		$this->clientService
697
-			->expects($this->once())
698
-			->method('newClient')
699
-			->willReturn($client);
700
-		$this->assertTrue(file_exists(__DIR__ . '/../../apps/testapp/appinfo/info.xml'));
701
-		$this->assertEquals('0.9', \OC_App::getAppVersionByPath(__DIR__ . '/../../apps/testapp/'));
702
-
703
-		$installer = $this->getInstaller();
704
-		$installer->downloadApp('testapp');
705
-		$this->assertTrue(file_exists(__DIR__ . '/../../apps/testapp/appinfo/info.xml'));
706
-		$this->assertEquals('0.8', \OC_App::getAppVersionByPath(__DIR__ . '/../../apps/testapp/'));
707
-	}
668
+                    ],
669
+                    [
670
+                        'download' => 'https://nextcloud.com',
671
+                    ],
672
+                ],
673
+            ],
674
+        ];
675
+        $this->appFetcher
676
+            ->expects($this->once())
677
+            ->method('get')
678
+            ->willReturn($appArray);
679
+        $realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
680
+        copy(__DIR__ . '/../data/testapp.0.8.tar.gz', $realTmpFile);
681
+        $this->tempManager
682
+            ->expects($this->once())
683
+            ->method('getTemporaryFile')
684
+            ->with('.tar.gz')
685
+            ->willReturn($realTmpFile);
686
+        $realTmpFolder = Server::get(ITempManager::class)->getTemporaryFolder();
687
+        $this->tempManager
688
+            ->expects($this->once())
689
+            ->method('getTemporaryFolder')
690
+            ->willReturn($realTmpFolder);
691
+        $client = $this->createMock(IClient::class);
692
+        $client
693
+            ->expects($this->once())
694
+            ->method('get')
695
+            ->with('https://example.com', ['sink' => $realTmpFile, 'timeout' => 120]);
696
+        $this->clientService
697
+            ->expects($this->once())
698
+            ->method('newClient')
699
+            ->willReturn($client);
700
+        $this->assertTrue(file_exists(__DIR__ . '/../../apps/testapp/appinfo/info.xml'));
701
+        $this->assertEquals('0.9', \OC_App::getAppVersionByPath(__DIR__ . '/../../apps/testapp/'));
702
+
703
+        $installer = $this->getInstaller();
704
+        $installer->downloadApp('testapp');
705
+        $this->assertTrue(file_exists(__DIR__ . '/../../apps/testapp/appinfo/info.xml'));
706
+        $this->assertEquals('0.8', \OC_App::getAppVersionByPath(__DIR__ . '/../../apps/testapp/'));
707
+    }
708 708
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -103,7 +103,7 @@  discard block
 block discarded – undo
103 103
 		);
104 104
 
105 105
 		// Extract app
106
-		$pathOfTestApp = __DIR__ . '/../data/testapp.zip';
106
+		$pathOfTestApp = __DIR__.'/../data/testapp.zip';
107 107
 		$tar = new ZIP($pathOfTestApp);
108 108
 		$tar->extract($installer->getInstallPath());
109 109
 
@@ -342,7 +342,7 @@  discard block
 block discarded – undo
342 342
 			->method('get')
343 343
 			->willReturn($appArray);
344 344
 		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
345
-		copy(__DIR__ . '/../data/testapp.tar.gz', $realTmpFile);
345
+		copy(__DIR__.'/../data/testapp.tar.gz', $realTmpFile);
346 346
 		$this->tempManager
347 347
 			->expects($this->once())
348 348
 			->method('getTemporaryFile')
@@ -420,14 +420,14 @@  discard block
 block discarded – undo
420 420
 			->method('get')
421 421
 			->willReturn($appArray);
422 422
 		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
423
-		copy(__DIR__ . '/../data/testapp1.tar.gz', $realTmpFile);
423
+		copy(__DIR__.'/../data/testapp1.tar.gz', $realTmpFile);
424 424
 		$this->tempManager
425 425
 			->expects($this->once())
426 426
 			->method('getTemporaryFile')
427 427
 			->with('.tar.gz')
428 428
 			->willReturn($realTmpFile);
429 429
 		$realTmpFolder = Server::get(ITempManager::class)->getTemporaryFolder();
430
-		mkdir($realTmpFolder . '/testfolder');
430
+		mkdir($realTmpFolder.'/testfolder');
431 431
 		$this->tempManager
432 432
 			->expects($this->once())
433 433
 			->method('getTemporaryFolder')
@@ -504,7 +504,7 @@  discard block
 block discarded – undo
504 504
 			->method('get')
505 505
 			->willReturn($appArray);
506 506
 		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
507
-		copy(__DIR__ . '/../data/testapp1.tar.gz', $realTmpFile);
507
+		copy(__DIR__.'/../data/testapp1.tar.gz', $realTmpFile);
508 508
 		$this->tempManager
509 509
 			->expects($this->once())
510 510
 			->method('getTemporaryFile')
@@ -583,7 +583,7 @@  discard block
 block discarded – undo
583 583
 			->method('get')
584 584
 			->willReturn($appArray);
585 585
 		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
586
-		copy(__DIR__ . '/../data/testapp.tar.gz', $realTmpFile);
586
+		copy(__DIR__.'/../data/testapp.tar.gz', $realTmpFile);
587 587
 		$this->tempManager
588 588
 			->expects($this->once())
589 589
 			->method('getTemporaryFile')
@@ -607,8 +607,8 @@  discard block
 block discarded – undo
607 607
 		$installer = $this->getInstaller();
608 608
 		$installer->downloadApp('testapp');
609 609
 
610
-		$this->assertTrue(file_exists(__DIR__ . '/../../apps/testapp/appinfo/info.xml'));
611
-		$this->assertEquals('0.9', \OC_App::getAppVersionByPath(__DIR__ . '/../../apps/testapp/'));
610
+		$this->assertTrue(file_exists(__DIR__.'/../../apps/testapp/appinfo/info.xml'));
611
+		$this->assertEquals('0.9', \OC_App::getAppVersionByPath(__DIR__.'/../../apps/testapp/'));
612 612
 	}
613 613
 
614 614
 
@@ -677,7 +677,7 @@  discard block
 block discarded – undo
677 677
 			->method('get')
678 678
 			->willReturn($appArray);
679 679
 		$realTmpFile = Server::get(ITempManager::class)->getTemporaryFile('.tar.gz');
680
-		copy(__DIR__ . '/../data/testapp.0.8.tar.gz', $realTmpFile);
680
+		copy(__DIR__.'/../data/testapp.0.8.tar.gz', $realTmpFile);
681 681
 		$this->tempManager
682 682
 			->expects($this->once())
683 683
 			->method('getTemporaryFile')
@@ -697,12 +697,12 @@  discard block
 block discarded – undo
697 697
 			->expects($this->once())
698 698
 			->method('newClient')
699 699
 			->willReturn($client);
700
-		$this->assertTrue(file_exists(__DIR__ . '/../../apps/testapp/appinfo/info.xml'));
701
-		$this->assertEquals('0.9', \OC_App::getAppVersionByPath(__DIR__ . '/../../apps/testapp/'));
700
+		$this->assertTrue(file_exists(__DIR__.'/../../apps/testapp/appinfo/info.xml'));
701
+		$this->assertEquals('0.9', \OC_App::getAppVersionByPath(__DIR__.'/../../apps/testapp/'));
702 702
 
703 703
 		$installer = $this->getInstaller();
704 704
 		$installer->downloadApp('testapp');
705
-		$this->assertTrue(file_exists(__DIR__ . '/../../apps/testapp/appinfo/info.xml'));
706
-		$this->assertEquals('0.8', \OC_App::getAppVersionByPath(__DIR__ . '/../../apps/testapp/'));
705
+		$this->assertTrue(file_exists(__DIR__.'/../../apps/testapp/appinfo/info.xml'));
706
+		$this->assertEquals('0.8', \OC_App::getAppVersionByPath(__DIR__.'/../../apps/testapp/'));
707 707
 	}
708 708
 }
Please login to merge, or discard this patch.
lib/private/Installer.php 2 patches
Indentation   +629 added lines, -629 removed lines patch added patch discarded remove patch
@@ -34,633 +34,633 @@
 block discarded – undo
34 34
  * This class provides the functionality needed to install, update and remove apps
35 35
  */
36 36
 class Installer {
37
-	private ?bool $isInstanceReadyForUpdates = null;
38
-	private ?array $apps = null;
39
-
40
-	public function __construct(
41
-		private AppFetcher $appFetcher,
42
-		private IClientService $clientService,
43
-		private ITempManager $tempManager,
44
-		private LoggerInterface $logger,
45
-		private IConfig $config,
46
-		private bool $isCLI,
47
-	) {
48
-	}
49
-
50
-	/**
51
-	 * Installs an app that is located in one of the app folders already
52
-	 *
53
-	 * @param string $appId App to install
54
-	 * @param bool $forceEnable
55
-	 * @throws \Exception
56
-	 * @return string app ID
57
-	 */
58
-	public function installApp(string $appId, bool $forceEnable = false): string {
59
-		$app = \OC_App::findAppInDirectories($appId);
60
-		if ($app === false) {
61
-			throw new \Exception('App not found in any app directory');
62
-		}
63
-
64
-		$basedir = $app['path'] . '/' . $appId;
65
-
66
-		if (is_file($basedir . '/appinfo/database.xml')) {
67
-			throw new \Exception('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
68
-		}
69
-
70
-		$l = \OCP\Util::getL10N('core');
71
-		$info = \OCP\Server::get(IAppManager::class)->getAppInfoByPath($basedir . '/appinfo/info.xml', $l->getLanguageCode());
72
-
73
-		if (!is_array($info)) {
74
-			throw new \Exception(
75
-				$l->t('App "%s" cannot be installed because appinfo file cannot be read.',
76
-					[$appId]
77
-				)
78
-			);
79
-		}
80
-
81
-		$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
82
-		$ignoreMax = $forceEnable || in_array($appId, $ignoreMaxApps, true);
83
-
84
-		$version = implode('.', \OCP\Util::getVersion());
85
-		if (!\OC_App::isAppCompatible($version, $info, $ignoreMax)) {
86
-			throw new \Exception(
87
-				// TODO $l
88
-				$l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
89
-					[$info['name']]
90
-				)
91
-			);
92
-		}
93
-
94
-		// check for required dependencies
95
-		\OC_App::checkAppDependencies($this->config, $l, $info, $ignoreMax);
96
-		/** @var Coordinator $coordinator */
97
-		$coordinator = \OC::$server->get(Coordinator::class);
98
-		$coordinator->runLazyRegistration($appId);
99
-		\OC_App::registerAutoloading($appId, $basedir);
100
-
101
-		$previousVersion = $this->config->getAppValue($info['id'], 'installed_version', false);
102
-		if ($previousVersion) {
103
-			OC_App::executeRepairSteps($appId, $info['repair-steps']['pre-migration']);
104
-		}
105
-
106
-		//install the database
107
-		$ms = new MigrationService($info['id'], \OCP\Server::get(Connection::class));
108
-		$ms->migrate('latest', !$previousVersion);
109
-
110
-		if ($previousVersion) {
111
-			OC_App::executeRepairSteps($appId, $info['repair-steps']['post-migration']);
112
-		}
113
-
114
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
115
-
116
-		//run appinfo/install.php
117
-		self::includeAppScript($basedir . '/appinfo/install.php');
118
-
119
-		OC_App::executeRepairSteps($appId, $info['repair-steps']['install']);
120
-
121
-		$config = \OCP\Server::get(IConfig::class);
122
-		//set the installed version
123
-		$config->setAppValue($info['id'], 'installed_version', \OCP\Server::get(IAppManager::class)->getAppVersion($info['id'], false));
124
-		$config->setAppValue($info['id'], 'enabled', 'no');
125
-
126
-		//set remote/public handlers
127
-		foreach ($info['remote'] as $name => $path) {
128
-			$config->setAppValue('core', 'remote_' . $name, $info['id'] . '/' . $path);
129
-		}
130
-		foreach ($info['public'] as $name => $path) {
131
-			$config->setAppValue('core', 'public_' . $name, $info['id'] . '/' . $path);
132
-		}
133
-
134
-		OC_App::setAppTypes($info['id']);
135
-
136
-		return $info['id'];
137
-	}
138
-
139
-	/**
140
-	 * Updates the specified app from the appstore
141
-	 *
142
-	 * @param bool $allowUnstable Allow unstable releases
143
-	 */
144
-	public function updateAppstoreApp(string $appId, bool $allowUnstable = false): bool {
145
-		if ($this->isUpdateAvailable($appId, $allowUnstable)) {
146
-			try {
147
-				$this->downloadApp($appId, $allowUnstable);
148
-			} catch (\Exception $e) {
149
-				$this->logger->error($e->getMessage(), [
150
-					'exception' => $e,
151
-				]);
152
-				return false;
153
-			}
154
-			return OC_App::updateApp($appId);
155
-		}
156
-
157
-		return false;
158
-	}
159
-
160
-	/**
161
-	 * Split the certificate file in individual certs
162
-	 *
163
-	 * @param string $cert
164
-	 * @return string[]
165
-	 */
166
-	private function splitCerts(string $cert): array {
167
-		preg_match_all('([\-]{3,}[\S\ ]+?[\-]{3,}[\S\s]+?[\-]{3,}[\S\ ]+?[\-]{3,})', $cert, $matches);
168
-
169
-		return $matches[0];
170
-	}
171
-
172
-	/**
173
-	 * Get the path where to install apps
174
-	 *
175
-	 * @throws \RuntimeException if an app folder is marked as writable but is missing permissions
176
-	 */
177
-	public function getInstallPath(): ?string {
178
-		foreach (\OC::$APPSROOTS as $dir) {
179
-			if (isset($dir['writable']) && $dir['writable'] === true) {
180
-				// Check if there is a writable install folder.
181
-				if (!is_writable($dir['path'])
182
-					|| !is_readable($dir['path'])
183
-				) {
184
-					throw new \RuntimeException(
185
-						'Cannot write into "apps" directory. This can usually be fixed by giving the web server write access to the apps directory or disabling the App Store in the config file.'
186
-					);
187
-				}
188
-				return $dir['path'];
189
-			}
190
-		}
191
-		return null;
192
-	}
193
-
194
-	/**
195
-	 * Downloads an app and puts it into the app directory
196
-	 *
197
-	 * @param string $appId
198
-	 * @param bool [$allowUnstable]
199
-	 *
200
-	 * @throws AppNotFoundException If the app is not found on the appstore
201
-	 * @throws \Exception If the installation was not successful
202
-	 */
203
-	public function downloadApp(string $appId, bool $allowUnstable = false): void {
204
-		$appId = strtolower($appId);
205
-
206
-		$installPath = $this->getInstallPath();
207
-		if ($installPath === null) {
208
-			throw new \Exception('No application directories are marked as writable.');
209
-		}
210
-
211
-		$apps = $this->appFetcher->get($allowUnstable);
212
-		foreach ($apps as $app) {
213
-			if ($app['id'] === $appId) {
214
-				// Load the certificate
215
-				$certificate = new X509();
216
-				$rootCrt = file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt');
217
-				$rootCrts = $this->splitCerts($rootCrt);
218
-				foreach ($rootCrts as $rootCrt) {
219
-					$certificate->loadCA($rootCrt);
220
-				}
221
-				$loadedCertificate = $certificate->loadX509($app['certificate']);
222
-
223
-				// Verify if the certificate has been revoked
224
-				$crl = new X509();
225
-				foreach ($rootCrts as $rootCrt) {
226
-					$crl->loadCA($rootCrt);
227
-				}
228
-				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
229
-				if ($crl->validateSignature() !== true) {
230
-					throw new \Exception('Could not validate CRL signature');
231
-				}
232
-				$csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
233
-				$revoked = $crl->getRevoked($csn);
234
-				if ($revoked !== false) {
235
-					throw new \Exception(
236
-						sprintf(
237
-							'Certificate "%s" has been revoked',
238
-							$csn
239
-						)
240
-					);
241
-				}
242
-
243
-				// Verify if the certificate has been issued by the Nextcloud Code Authority CA
244
-				if ($certificate->validateSignature() !== true) {
245
-					throw new \Exception(
246
-						sprintf(
247
-							'App with id %s has a certificate not issued by a trusted Code Signing Authority',
248
-							$appId
249
-						)
250
-					);
251
-				}
252
-
253
-				// Verify if the certificate is issued for the requested app id
254
-				$certInfo = openssl_x509_parse($app['certificate']);
255
-				if (!isset($certInfo['subject']['CN'])) {
256
-					throw new \Exception(
257
-						sprintf(
258
-							'App with id %s has a cert with no CN',
259
-							$appId
260
-						)
261
-					);
262
-				}
263
-				if ($certInfo['subject']['CN'] !== $appId) {
264
-					throw new \Exception(
265
-						sprintf(
266
-							'App with id %s has a cert issued to %s',
267
-							$appId,
268
-							$certInfo['subject']['CN']
269
-						)
270
-					);
271
-				}
272
-
273
-				// Download the release
274
-				$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
275
-				if ($tempFile === false) {
276
-					throw new \RuntimeException('Could not create temporary file for downloading app archive.');
277
-				}
278
-
279
-				$timeout = $this->isCLI ? 0 : 120;
280
-				$client = $this->clientService->newClient();
281
-				$client->get($app['releases'][0]['download'], ['sink' => $tempFile, 'timeout' => $timeout]);
282
-
283
-				// Check if the signature actually matches the downloaded content
284
-				$certificate = openssl_get_publickey($app['certificate']);
285
-				$verified = openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512) === 1;
286
-
287
-				if ($verified === true) {
288
-					// Seems to match, let's proceed
289
-					$extractDir = $this->tempManager->getTemporaryFolder();
290
-					if ($extractDir === false) {
291
-						throw new \RuntimeException('Could not create temporary directory for unpacking app.');
292
-					}
293
-
294
-					$archive = new TAR($tempFile);
295
-					if (!$archive->extract($extractDir)) {
296
-						$errorMessage = 'Could not extract app ' . $appId;
297
-
298
-						$archiveError = $archive->getError();
299
-						if ($archiveError instanceof \PEAR_Error) {
300
-							$errorMessage .= ': ' . $archiveError->getMessage();
301
-						}
302
-
303
-						throw new \Exception($errorMessage);
304
-					}
305
-					$allFiles = scandir($extractDir);
306
-					$folders = array_diff($allFiles, ['.', '..']);
307
-					$folders = array_values($folders);
308
-
309
-					if (count($folders) < 1) {
310
-						throw new \Exception(
311
-							sprintf(
312
-								'Extracted app %s has no folders',
313
-								$appId
314
-							)
315
-						);
316
-					}
317
-
318
-					if (count($folders) > 1) {
319
-						throw new \Exception(
320
-							sprintf(
321
-								'Extracted app %s has more than 1 folder',
322
-								$appId
323
-							)
324
-						);
325
-					}
326
-
327
-					// Check if appinfo/info.xml has the same app ID as well
328
-					$xml = simplexml_load_string(file_get_contents($extractDir . '/' . $folders[0] . '/appinfo/info.xml'));
329
-
330
-					if ($xml === false) {
331
-						throw new \Exception(
332
-							sprintf(
333
-								'Failed to load info.xml for app id %s',
334
-								$appId,
335
-							)
336
-						);
337
-					}
338
-
339
-					if ((string)$xml->id !== $appId) {
340
-						throw new \Exception(
341
-							sprintf(
342
-								'App for id %s has a wrong app ID in info.xml: %s',
343
-								$appId,
344
-								(string)$xml->id
345
-							)
346
-						);
347
-					}
348
-
349
-					// Check if the version is lower than before
350
-					$currentVersion = \OCP\Server::get(IAppManager::class)->getAppVersion($appId, true);
351
-					$newVersion = (string)$xml->version;
352
-					if (version_compare($currentVersion, $newVersion) === 1) {
353
-						throw new \Exception(
354
-							sprintf(
355
-								'App for id %s has version %s and tried to update to lower version %s',
356
-								$appId,
357
-								$currentVersion,
358
-								$newVersion
359
-							)
360
-						);
361
-					}
362
-
363
-					$baseDir = $installPath . '/' . $appId;
364
-					// Remove old app with the ID if existent
365
-					Files::rmdirr($baseDir);
366
-					// Move to app folder
367
-					if (@mkdir($baseDir)) {
368
-						$extractDir .= '/' . $folders[0];
369
-					}
370
-					// otherwise we just copy the outer directory
371
-					$this->copyRecursive($extractDir, $baseDir);
372
-					Files::rmdirr($extractDir);
373
-					if (function_exists('opcache_reset')) {
374
-						opcache_reset();
375
-					}
376
-					return;
377
-				}
378
-				// Signature does not match
379
-				throw new \Exception(
380
-					sprintf(
381
-						'App with id %s has invalid signature',
382
-						$appId
383
-					)
384
-				);
385
-			}
386
-		}
387
-
388
-		throw new AppNotFoundException(
389
-			sprintf(
390
-				'Could not download app %s, it was not found on the appstore',
391
-				$appId
392
-			)
393
-		);
394
-	}
395
-
396
-	/**
397
-	 * Check if an update for the app is available
398
-	 *
399
-	 * @param string $appId
400
-	 * @param bool $allowUnstable
401
-	 * @return string|false false or the version number of the update
402
-	 */
403
-	public function isUpdateAvailable($appId, $allowUnstable = false): string|false {
404
-		if ($this->isInstanceReadyForUpdates === null) {
405
-			$installPath = $this->getInstallPath();
406
-			if ($installPath === null) {
407
-				$this->isInstanceReadyForUpdates = false;
408
-			} else {
409
-				$this->isInstanceReadyForUpdates = true;
410
-			}
411
-		}
412
-
413
-		if ($this->isInstanceReadyForUpdates === false) {
414
-			return false;
415
-		}
416
-
417
-		if ($this->isInstalledFromGit($appId) === true) {
418
-			return false;
419
-		}
420
-
421
-		if ($this->apps === null) {
422
-			$this->apps = $this->appFetcher->get($allowUnstable);
423
-		}
424
-
425
-		foreach ($this->apps as $app) {
426
-			if ($app['id'] === $appId) {
427
-				$currentVersion = \OCP\Server::get(IAppManager::class)->getAppVersion($appId, true);
428
-
429
-				if (!isset($app['releases'][0]['version'])) {
430
-					return false;
431
-				}
432
-				$newestVersion = $app['releases'][0]['version'];
433
-				if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) {
434
-					return $newestVersion;
435
-				} else {
436
-					return false;
437
-				}
438
-			}
439
-		}
440
-
441
-		return false;
442
-	}
443
-
444
-	/**
445
-	 * Check if app has been installed from git
446
-	 *
447
-	 * The function will check if the path contains a .git folder
448
-	 */
449
-	private function isInstalledFromGit(string $appId): bool {
450
-		$app = \OC_App::findAppInDirectories($appId);
451
-		if ($app === false) {
452
-			return false;
453
-		}
454
-		$basedir = $app['path'] . '/' . $appId;
455
-		return file_exists($basedir . '/.git/');
456
-	}
457
-
458
-	/**
459
-	 * Check if app is already downloaded
460
-	 *
461
-	 * The function will check if the app is already downloaded in the apps repository
462
-	 */
463
-	public function isDownloaded(string $name): bool {
464
-		foreach (\OC::$APPSROOTS as $dir) {
465
-			$dirToTest = $dir['path'];
466
-			$dirToTest .= '/';
467
-			$dirToTest .= $name;
468
-			$dirToTest .= '/';
469
-
470
-			if (is_dir($dirToTest)) {
471
-				return true;
472
-			}
473
-		}
474
-
475
-		return false;
476
-	}
477
-
478
-	/**
479
-	 * Removes an app
480
-	 *
481
-	 * This function works as follows
482
-	 *   -# call uninstall repair steps
483
-	 *   -# removing the files
484
-	 *
485
-	 * The function will not delete preferences, tables and the configuration,
486
-	 * this has to be done by the function oc_app_uninstall().
487
-	 */
488
-	public function removeApp(string $appId): bool {
489
-		if ($this->isDownloaded($appId)) {
490
-			if (\OCP\Server::get(IAppManager::class)->isShipped($appId)) {
491
-				return false;
492
-			}
493
-
494
-			$installPath = $this->getInstallPath();
495
-			if ($installPath === null) {
496
-				$this->logger->error('No application directories are marked as writable.', ['app' => 'core']);
497
-				return false;
498
-			}
499
-			$appDir = $installPath . '/' . $appId;
500
-			Files::rmdirr($appDir);
501
-			return true;
502
-		} else {
503
-			$this->logger->error('can\'t remove app ' . $appId . '. It is not installed.');
504
-
505
-			return false;
506
-		}
507
-	}
508
-
509
-	/**
510
-	 * Installs the app within the bundle and marks the bundle as installed
511
-	 *
512
-	 * @throws \Exception If app could not get installed
513
-	 */
514
-	public function installAppBundle(Bundle $bundle): void {
515
-		$appIds = $bundle->getAppIdentifiers();
516
-		foreach ($appIds as $appId) {
517
-			if (!$this->isDownloaded($appId)) {
518
-				$this->downloadApp($appId);
519
-			}
520
-			$this->installApp($appId);
521
-			$app = new OC_App();
522
-			$app->enable($appId);
523
-		}
524
-		$bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
525
-		$bundles[] = $bundle->getIdentifier();
526
-		$this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
527
-	}
528
-
529
-	/**
530
-	 * Installs shipped apps
531
-	 *
532
-	 * This function installs all apps found in the 'apps' directory that should be enabled by default;
533
-	 * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
534
-	 *                         working ownCloud at the end instead of an aborted update.
535
-	 * @return array Array of error messages (appid => Exception)
536
-	 */
537
-	public static function installShippedApps(bool $softErrors = false, ?IOutput $output = null): array {
538
-		if ($output instanceof IOutput) {
539
-			$output->debug('Installing shipped apps');
540
-		}
541
-		$appManager = \OCP\Server::get(IAppManager::class);
542
-		$config = \OCP\Server::get(IConfig::class);
543
-		$errors = [];
544
-		foreach (\OC::$APPSROOTS as $app_dir) {
545
-			if ($dir = opendir($app_dir['path'])) {
546
-				while (false !== ($filename = readdir($dir))) {
547
-					if ($filename[0] !== '.' and is_dir($app_dir['path'] . "/$filename")) {
548
-						if (file_exists($app_dir['path'] . "/$filename/appinfo/info.xml")) {
549
-							if ($config->getAppValue($filename, 'installed_version', null) === null) {
550
-								$enabled = $appManager->isDefaultEnabled($filename);
551
-								if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
552
-									  && $config->getAppValue($filename, 'enabled') !== 'no') {
553
-									if ($softErrors) {
554
-										try {
555
-											Installer::installShippedApp($filename, $output);
556
-										} catch (HintException $e) {
557
-											if ($e->getPrevious() instanceof TableExistsException) {
558
-												$errors[$filename] = $e;
559
-												continue;
560
-											}
561
-											throw $e;
562
-										}
563
-									} else {
564
-										Installer::installShippedApp($filename, $output);
565
-									}
566
-									$config->setAppValue($filename, 'enabled', 'yes');
567
-								}
568
-							}
569
-						}
570
-					}
571
-				}
572
-				closedir($dir);
573
-			}
574
-		}
575
-
576
-		return $errors;
577
-	}
578
-
579
-	/**
580
-	 * install an app already placed in the app folder
581
-	 */
582
-	public static function installShippedApp(string $app, ?IOutput $output = null): string|false {
583
-		if ($output instanceof IOutput) {
584
-			$output->debug('Installing ' . $app);
585
-		}
586
-
587
-		$appManager = \OCP\Server::get(IAppManager::class);
588
-		$config = \OCP\Server::get(IConfig::class);
589
-
590
-		$appPath = $appManager->getAppPath($app);
591
-		\OC_App::registerAutoloading($app, $appPath);
592
-
593
-		$ms = new MigrationService($app, \OCP\Server::get(Connection::class));
594
-		if ($output instanceof IOutput) {
595
-			$ms->setOutput($output);
596
-		}
597
-		$previousVersion = $config->getAppValue($app, 'installed_version', false);
598
-		$ms->migrate('latest', !$previousVersion);
599
-
600
-		//run appinfo/install.php
601
-		self::includeAppScript("$appPath/appinfo/install.php");
602
-
603
-		$info = \OCP\Server::get(IAppManager::class)->getAppInfo($app);
604
-		if (is_null($info)) {
605
-			return false;
606
-		}
607
-		if ($output instanceof IOutput) {
608
-			$output->debug('Registering tasks of ' . $app);
609
-		}
610
-		\OC_App::setupBackgroundJobs($info['background-jobs']);
611
-
612
-		OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
613
-
614
-		$config->setAppValue($app, 'installed_version', \OCP\Server::get(IAppManager::class)->getAppVersion($app));
615
-		if (array_key_exists('ocsid', $info)) {
616
-			$config->setAppValue($app, 'ocsid', $info['ocsid']);
617
-		}
618
-
619
-		//set remote/public handlers
620
-		foreach ($info['remote'] as $name => $path) {
621
-			$config->setAppValue('core', 'remote_' . $name, $app . '/' . $path);
622
-		}
623
-		foreach ($info['public'] as $name => $path) {
624
-			$config->setAppValue('core', 'public_' . $name, $app . '/' . $path);
625
-		}
626
-
627
-		OC_App::setAppTypes($info['id']);
628
-
629
-		return $info['id'];
630
-	}
631
-
632
-	private static function includeAppScript(string $script): void {
633
-		if (file_exists($script)) {
634
-			include $script;
635
-		}
636
-	}
637
-
638
-	/**
639
-	 * Recursive copying of local folders.
640
-	 *
641
-	 * @param string $src source folder
642
-	 * @param string $dest target folder
643
-	 */
644
-	private function copyRecursive(string $src, string $dest): void {
645
-		if (!file_exists($src)) {
646
-			return;
647
-		}
648
-
649
-		if (is_dir($src)) {
650
-			if (!is_dir($dest)) {
651
-				mkdir($dest);
652
-			}
653
-			$files = scandir($src);
654
-			foreach ($files as $file) {
655
-				if ($file != '.' && $file != '..') {
656
-					$this->copyRecursive("$src/$file", "$dest/$file");
657
-				}
658
-			}
659
-		} else {
660
-			$validator = Server::get(FilenameValidator::class);
661
-			if (!$validator->isForbidden($src)) {
662
-				copy($src, $dest);
663
-			}
664
-		}
665
-	}
37
+    private ?bool $isInstanceReadyForUpdates = null;
38
+    private ?array $apps = null;
39
+
40
+    public function __construct(
41
+        private AppFetcher $appFetcher,
42
+        private IClientService $clientService,
43
+        private ITempManager $tempManager,
44
+        private LoggerInterface $logger,
45
+        private IConfig $config,
46
+        private bool $isCLI,
47
+    ) {
48
+    }
49
+
50
+    /**
51
+     * Installs an app that is located in one of the app folders already
52
+     *
53
+     * @param string $appId App to install
54
+     * @param bool $forceEnable
55
+     * @throws \Exception
56
+     * @return string app ID
57
+     */
58
+    public function installApp(string $appId, bool $forceEnable = false): string {
59
+        $app = \OC_App::findAppInDirectories($appId);
60
+        if ($app === false) {
61
+            throw new \Exception('App not found in any app directory');
62
+        }
63
+
64
+        $basedir = $app['path'] . '/' . $appId;
65
+
66
+        if (is_file($basedir . '/appinfo/database.xml')) {
67
+            throw new \Exception('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
68
+        }
69
+
70
+        $l = \OCP\Util::getL10N('core');
71
+        $info = \OCP\Server::get(IAppManager::class)->getAppInfoByPath($basedir . '/appinfo/info.xml', $l->getLanguageCode());
72
+
73
+        if (!is_array($info)) {
74
+            throw new \Exception(
75
+                $l->t('App "%s" cannot be installed because appinfo file cannot be read.',
76
+                    [$appId]
77
+                )
78
+            );
79
+        }
80
+
81
+        $ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
82
+        $ignoreMax = $forceEnable || in_array($appId, $ignoreMaxApps, true);
83
+
84
+        $version = implode('.', \OCP\Util::getVersion());
85
+        if (!\OC_App::isAppCompatible($version, $info, $ignoreMax)) {
86
+            throw new \Exception(
87
+                // TODO $l
88
+                $l->t('App "%s" cannot be installed because it is not compatible with this version of the server.',
89
+                    [$info['name']]
90
+                )
91
+            );
92
+        }
93
+
94
+        // check for required dependencies
95
+        \OC_App::checkAppDependencies($this->config, $l, $info, $ignoreMax);
96
+        /** @var Coordinator $coordinator */
97
+        $coordinator = \OC::$server->get(Coordinator::class);
98
+        $coordinator->runLazyRegistration($appId);
99
+        \OC_App::registerAutoloading($appId, $basedir);
100
+
101
+        $previousVersion = $this->config->getAppValue($info['id'], 'installed_version', false);
102
+        if ($previousVersion) {
103
+            OC_App::executeRepairSteps($appId, $info['repair-steps']['pre-migration']);
104
+        }
105
+
106
+        //install the database
107
+        $ms = new MigrationService($info['id'], \OCP\Server::get(Connection::class));
108
+        $ms->migrate('latest', !$previousVersion);
109
+
110
+        if ($previousVersion) {
111
+            OC_App::executeRepairSteps($appId, $info['repair-steps']['post-migration']);
112
+        }
113
+
114
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
115
+
116
+        //run appinfo/install.php
117
+        self::includeAppScript($basedir . '/appinfo/install.php');
118
+
119
+        OC_App::executeRepairSteps($appId, $info['repair-steps']['install']);
120
+
121
+        $config = \OCP\Server::get(IConfig::class);
122
+        //set the installed version
123
+        $config->setAppValue($info['id'], 'installed_version', \OCP\Server::get(IAppManager::class)->getAppVersion($info['id'], false));
124
+        $config->setAppValue($info['id'], 'enabled', 'no');
125
+
126
+        //set remote/public handlers
127
+        foreach ($info['remote'] as $name => $path) {
128
+            $config->setAppValue('core', 'remote_' . $name, $info['id'] . '/' . $path);
129
+        }
130
+        foreach ($info['public'] as $name => $path) {
131
+            $config->setAppValue('core', 'public_' . $name, $info['id'] . '/' . $path);
132
+        }
133
+
134
+        OC_App::setAppTypes($info['id']);
135
+
136
+        return $info['id'];
137
+    }
138
+
139
+    /**
140
+     * Updates the specified app from the appstore
141
+     *
142
+     * @param bool $allowUnstable Allow unstable releases
143
+     */
144
+    public function updateAppstoreApp(string $appId, bool $allowUnstable = false): bool {
145
+        if ($this->isUpdateAvailable($appId, $allowUnstable)) {
146
+            try {
147
+                $this->downloadApp($appId, $allowUnstable);
148
+            } catch (\Exception $e) {
149
+                $this->logger->error($e->getMessage(), [
150
+                    'exception' => $e,
151
+                ]);
152
+                return false;
153
+            }
154
+            return OC_App::updateApp($appId);
155
+        }
156
+
157
+        return false;
158
+    }
159
+
160
+    /**
161
+     * Split the certificate file in individual certs
162
+     *
163
+     * @param string $cert
164
+     * @return string[]
165
+     */
166
+    private function splitCerts(string $cert): array {
167
+        preg_match_all('([\-]{3,}[\S\ ]+?[\-]{3,}[\S\s]+?[\-]{3,}[\S\ ]+?[\-]{3,})', $cert, $matches);
168
+
169
+        return $matches[0];
170
+    }
171
+
172
+    /**
173
+     * Get the path where to install apps
174
+     *
175
+     * @throws \RuntimeException if an app folder is marked as writable but is missing permissions
176
+     */
177
+    public function getInstallPath(): ?string {
178
+        foreach (\OC::$APPSROOTS as $dir) {
179
+            if (isset($dir['writable']) && $dir['writable'] === true) {
180
+                // Check if there is a writable install folder.
181
+                if (!is_writable($dir['path'])
182
+                    || !is_readable($dir['path'])
183
+                ) {
184
+                    throw new \RuntimeException(
185
+                        'Cannot write into "apps" directory. This can usually be fixed by giving the web server write access to the apps directory or disabling the App Store in the config file.'
186
+                    );
187
+                }
188
+                return $dir['path'];
189
+            }
190
+        }
191
+        return null;
192
+    }
193
+
194
+    /**
195
+     * Downloads an app and puts it into the app directory
196
+     *
197
+     * @param string $appId
198
+     * @param bool [$allowUnstable]
199
+     *
200
+     * @throws AppNotFoundException If the app is not found on the appstore
201
+     * @throws \Exception If the installation was not successful
202
+     */
203
+    public function downloadApp(string $appId, bool $allowUnstable = false): void {
204
+        $appId = strtolower($appId);
205
+
206
+        $installPath = $this->getInstallPath();
207
+        if ($installPath === null) {
208
+            throw new \Exception('No application directories are marked as writable.');
209
+        }
210
+
211
+        $apps = $this->appFetcher->get($allowUnstable);
212
+        foreach ($apps as $app) {
213
+            if ($app['id'] === $appId) {
214
+                // Load the certificate
215
+                $certificate = new X509();
216
+                $rootCrt = file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt');
217
+                $rootCrts = $this->splitCerts($rootCrt);
218
+                foreach ($rootCrts as $rootCrt) {
219
+                    $certificate->loadCA($rootCrt);
220
+                }
221
+                $loadedCertificate = $certificate->loadX509($app['certificate']);
222
+
223
+                // Verify if the certificate has been revoked
224
+                $crl = new X509();
225
+                foreach ($rootCrts as $rootCrt) {
226
+                    $crl->loadCA($rootCrt);
227
+                }
228
+                $crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
229
+                if ($crl->validateSignature() !== true) {
230
+                    throw new \Exception('Could not validate CRL signature');
231
+                }
232
+                $csn = $loadedCertificate['tbsCertificate']['serialNumber']->toString();
233
+                $revoked = $crl->getRevoked($csn);
234
+                if ($revoked !== false) {
235
+                    throw new \Exception(
236
+                        sprintf(
237
+                            'Certificate "%s" has been revoked',
238
+                            $csn
239
+                        )
240
+                    );
241
+                }
242
+
243
+                // Verify if the certificate has been issued by the Nextcloud Code Authority CA
244
+                if ($certificate->validateSignature() !== true) {
245
+                    throw new \Exception(
246
+                        sprintf(
247
+                            'App with id %s has a certificate not issued by a trusted Code Signing Authority',
248
+                            $appId
249
+                        )
250
+                    );
251
+                }
252
+
253
+                // Verify if the certificate is issued for the requested app id
254
+                $certInfo = openssl_x509_parse($app['certificate']);
255
+                if (!isset($certInfo['subject']['CN'])) {
256
+                    throw new \Exception(
257
+                        sprintf(
258
+                            'App with id %s has a cert with no CN',
259
+                            $appId
260
+                        )
261
+                    );
262
+                }
263
+                if ($certInfo['subject']['CN'] !== $appId) {
264
+                    throw new \Exception(
265
+                        sprintf(
266
+                            'App with id %s has a cert issued to %s',
267
+                            $appId,
268
+                            $certInfo['subject']['CN']
269
+                        )
270
+                    );
271
+                }
272
+
273
+                // Download the release
274
+                $tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
275
+                if ($tempFile === false) {
276
+                    throw new \RuntimeException('Could not create temporary file for downloading app archive.');
277
+                }
278
+
279
+                $timeout = $this->isCLI ? 0 : 120;
280
+                $client = $this->clientService->newClient();
281
+                $client->get($app['releases'][0]['download'], ['sink' => $tempFile, 'timeout' => $timeout]);
282
+
283
+                // Check if the signature actually matches the downloaded content
284
+                $certificate = openssl_get_publickey($app['certificate']);
285
+                $verified = openssl_verify(file_get_contents($tempFile), base64_decode($app['releases'][0]['signature']), $certificate, OPENSSL_ALGO_SHA512) === 1;
286
+
287
+                if ($verified === true) {
288
+                    // Seems to match, let's proceed
289
+                    $extractDir = $this->tempManager->getTemporaryFolder();
290
+                    if ($extractDir === false) {
291
+                        throw new \RuntimeException('Could not create temporary directory for unpacking app.');
292
+                    }
293
+
294
+                    $archive = new TAR($tempFile);
295
+                    if (!$archive->extract($extractDir)) {
296
+                        $errorMessage = 'Could not extract app ' . $appId;
297
+
298
+                        $archiveError = $archive->getError();
299
+                        if ($archiveError instanceof \PEAR_Error) {
300
+                            $errorMessage .= ': ' . $archiveError->getMessage();
301
+                        }
302
+
303
+                        throw new \Exception($errorMessage);
304
+                    }
305
+                    $allFiles = scandir($extractDir);
306
+                    $folders = array_diff($allFiles, ['.', '..']);
307
+                    $folders = array_values($folders);
308
+
309
+                    if (count($folders) < 1) {
310
+                        throw new \Exception(
311
+                            sprintf(
312
+                                'Extracted app %s has no folders',
313
+                                $appId
314
+                            )
315
+                        );
316
+                    }
317
+
318
+                    if (count($folders) > 1) {
319
+                        throw new \Exception(
320
+                            sprintf(
321
+                                'Extracted app %s has more than 1 folder',
322
+                                $appId
323
+                            )
324
+                        );
325
+                    }
326
+
327
+                    // Check if appinfo/info.xml has the same app ID as well
328
+                    $xml = simplexml_load_string(file_get_contents($extractDir . '/' . $folders[0] . '/appinfo/info.xml'));
329
+
330
+                    if ($xml === false) {
331
+                        throw new \Exception(
332
+                            sprintf(
333
+                                'Failed to load info.xml for app id %s',
334
+                                $appId,
335
+                            )
336
+                        );
337
+                    }
338
+
339
+                    if ((string)$xml->id !== $appId) {
340
+                        throw new \Exception(
341
+                            sprintf(
342
+                                'App for id %s has a wrong app ID in info.xml: %s',
343
+                                $appId,
344
+                                (string)$xml->id
345
+                            )
346
+                        );
347
+                    }
348
+
349
+                    // Check if the version is lower than before
350
+                    $currentVersion = \OCP\Server::get(IAppManager::class)->getAppVersion($appId, true);
351
+                    $newVersion = (string)$xml->version;
352
+                    if (version_compare($currentVersion, $newVersion) === 1) {
353
+                        throw new \Exception(
354
+                            sprintf(
355
+                                'App for id %s has version %s and tried to update to lower version %s',
356
+                                $appId,
357
+                                $currentVersion,
358
+                                $newVersion
359
+                            )
360
+                        );
361
+                    }
362
+
363
+                    $baseDir = $installPath . '/' . $appId;
364
+                    // Remove old app with the ID if existent
365
+                    Files::rmdirr($baseDir);
366
+                    // Move to app folder
367
+                    if (@mkdir($baseDir)) {
368
+                        $extractDir .= '/' . $folders[0];
369
+                    }
370
+                    // otherwise we just copy the outer directory
371
+                    $this->copyRecursive($extractDir, $baseDir);
372
+                    Files::rmdirr($extractDir);
373
+                    if (function_exists('opcache_reset')) {
374
+                        opcache_reset();
375
+                    }
376
+                    return;
377
+                }
378
+                // Signature does not match
379
+                throw new \Exception(
380
+                    sprintf(
381
+                        'App with id %s has invalid signature',
382
+                        $appId
383
+                    )
384
+                );
385
+            }
386
+        }
387
+
388
+        throw new AppNotFoundException(
389
+            sprintf(
390
+                'Could not download app %s, it was not found on the appstore',
391
+                $appId
392
+            )
393
+        );
394
+    }
395
+
396
+    /**
397
+     * Check if an update for the app is available
398
+     *
399
+     * @param string $appId
400
+     * @param bool $allowUnstable
401
+     * @return string|false false or the version number of the update
402
+     */
403
+    public function isUpdateAvailable($appId, $allowUnstable = false): string|false {
404
+        if ($this->isInstanceReadyForUpdates === null) {
405
+            $installPath = $this->getInstallPath();
406
+            if ($installPath === null) {
407
+                $this->isInstanceReadyForUpdates = false;
408
+            } else {
409
+                $this->isInstanceReadyForUpdates = true;
410
+            }
411
+        }
412
+
413
+        if ($this->isInstanceReadyForUpdates === false) {
414
+            return false;
415
+        }
416
+
417
+        if ($this->isInstalledFromGit($appId) === true) {
418
+            return false;
419
+        }
420
+
421
+        if ($this->apps === null) {
422
+            $this->apps = $this->appFetcher->get($allowUnstable);
423
+        }
424
+
425
+        foreach ($this->apps as $app) {
426
+            if ($app['id'] === $appId) {
427
+                $currentVersion = \OCP\Server::get(IAppManager::class)->getAppVersion($appId, true);
428
+
429
+                if (!isset($app['releases'][0]['version'])) {
430
+                    return false;
431
+                }
432
+                $newestVersion = $app['releases'][0]['version'];
433
+                if ($currentVersion !== '0' && version_compare($newestVersion, $currentVersion, '>')) {
434
+                    return $newestVersion;
435
+                } else {
436
+                    return false;
437
+                }
438
+            }
439
+        }
440
+
441
+        return false;
442
+    }
443
+
444
+    /**
445
+     * Check if app has been installed from git
446
+     *
447
+     * The function will check if the path contains a .git folder
448
+     */
449
+    private function isInstalledFromGit(string $appId): bool {
450
+        $app = \OC_App::findAppInDirectories($appId);
451
+        if ($app === false) {
452
+            return false;
453
+        }
454
+        $basedir = $app['path'] . '/' . $appId;
455
+        return file_exists($basedir . '/.git/');
456
+    }
457
+
458
+    /**
459
+     * Check if app is already downloaded
460
+     *
461
+     * The function will check if the app is already downloaded in the apps repository
462
+     */
463
+    public function isDownloaded(string $name): bool {
464
+        foreach (\OC::$APPSROOTS as $dir) {
465
+            $dirToTest = $dir['path'];
466
+            $dirToTest .= '/';
467
+            $dirToTest .= $name;
468
+            $dirToTest .= '/';
469
+
470
+            if (is_dir($dirToTest)) {
471
+                return true;
472
+            }
473
+        }
474
+
475
+        return false;
476
+    }
477
+
478
+    /**
479
+     * Removes an app
480
+     *
481
+     * This function works as follows
482
+     *   -# call uninstall repair steps
483
+     *   -# removing the files
484
+     *
485
+     * The function will not delete preferences, tables and the configuration,
486
+     * this has to be done by the function oc_app_uninstall().
487
+     */
488
+    public function removeApp(string $appId): bool {
489
+        if ($this->isDownloaded($appId)) {
490
+            if (\OCP\Server::get(IAppManager::class)->isShipped($appId)) {
491
+                return false;
492
+            }
493
+
494
+            $installPath = $this->getInstallPath();
495
+            if ($installPath === null) {
496
+                $this->logger->error('No application directories are marked as writable.', ['app' => 'core']);
497
+                return false;
498
+            }
499
+            $appDir = $installPath . '/' . $appId;
500
+            Files::rmdirr($appDir);
501
+            return true;
502
+        } else {
503
+            $this->logger->error('can\'t remove app ' . $appId . '. It is not installed.');
504
+
505
+            return false;
506
+        }
507
+    }
508
+
509
+    /**
510
+     * Installs the app within the bundle and marks the bundle as installed
511
+     *
512
+     * @throws \Exception If app could not get installed
513
+     */
514
+    public function installAppBundle(Bundle $bundle): void {
515
+        $appIds = $bundle->getAppIdentifiers();
516
+        foreach ($appIds as $appId) {
517
+            if (!$this->isDownloaded($appId)) {
518
+                $this->downloadApp($appId);
519
+            }
520
+            $this->installApp($appId);
521
+            $app = new OC_App();
522
+            $app->enable($appId);
523
+        }
524
+        $bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true);
525
+        $bundles[] = $bundle->getIdentifier();
526
+        $this->config->setAppValue('core', 'installed.bundles', json_encode($bundles));
527
+    }
528
+
529
+    /**
530
+     * Installs shipped apps
531
+     *
532
+     * This function installs all apps found in the 'apps' directory that should be enabled by default;
533
+     * @param bool $softErrors When updating we ignore errors and simply log them, better to have a
534
+     *                         working ownCloud at the end instead of an aborted update.
535
+     * @return array Array of error messages (appid => Exception)
536
+     */
537
+    public static function installShippedApps(bool $softErrors = false, ?IOutput $output = null): array {
538
+        if ($output instanceof IOutput) {
539
+            $output->debug('Installing shipped apps');
540
+        }
541
+        $appManager = \OCP\Server::get(IAppManager::class);
542
+        $config = \OCP\Server::get(IConfig::class);
543
+        $errors = [];
544
+        foreach (\OC::$APPSROOTS as $app_dir) {
545
+            if ($dir = opendir($app_dir['path'])) {
546
+                while (false !== ($filename = readdir($dir))) {
547
+                    if ($filename[0] !== '.' and is_dir($app_dir['path'] . "/$filename")) {
548
+                        if (file_exists($app_dir['path'] . "/$filename/appinfo/info.xml")) {
549
+                            if ($config->getAppValue($filename, 'installed_version', null) === null) {
550
+                                $enabled = $appManager->isDefaultEnabled($filename);
551
+                                if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
552
+                                      && $config->getAppValue($filename, 'enabled') !== 'no') {
553
+                                    if ($softErrors) {
554
+                                        try {
555
+                                            Installer::installShippedApp($filename, $output);
556
+                                        } catch (HintException $e) {
557
+                                            if ($e->getPrevious() instanceof TableExistsException) {
558
+                                                $errors[$filename] = $e;
559
+                                                continue;
560
+                                            }
561
+                                            throw $e;
562
+                                        }
563
+                                    } else {
564
+                                        Installer::installShippedApp($filename, $output);
565
+                                    }
566
+                                    $config->setAppValue($filename, 'enabled', 'yes');
567
+                                }
568
+                            }
569
+                        }
570
+                    }
571
+                }
572
+                closedir($dir);
573
+            }
574
+        }
575
+
576
+        return $errors;
577
+    }
578
+
579
+    /**
580
+     * install an app already placed in the app folder
581
+     */
582
+    public static function installShippedApp(string $app, ?IOutput $output = null): string|false {
583
+        if ($output instanceof IOutput) {
584
+            $output->debug('Installing ' . $app);
585
+        }
586
+
587
+        $appManager = \OCP\Server::get(IAppManager::class);
588
+        $config = \OCP\Server::get(IConfig::class);
589
+
590
+        $appPath = $appManager->getAppPath($app);
591
+        \OC_App::registerAutoloading($app, $appPath);
592
+
593
+        $ms = new MigrationService($app, \OCP\Server::get(Connection::class));
594
+        if ($output instanceof IOutput) {
595
+            $ms->setOutput($output);
596
+        }
597
+        $previousVersion = $config->getAppValue($app, 'installed_version', false);
598
+        $ms->migrate('latest', !$previousVersion);
599
+
600
+        //run appinfo/install.php
601
+        self::includeAppScript("$appPath/appinfo/install.php");
602
+
603
+        $info = \OCP\Server::get(IAppManager::class)->getAppInfo($app);
604
+        if (is_null($info)) {
605
+            return false;
606
+        }
607
+        if ($output instanceof IOutput) {
608
+            $output->debug('Registering tasks of ' . $app);
609
+        }
610
+        \OC_App::setupBackgroundJobs($info['background-jobs']);
611
+
612
+        OC_App::executeRepairSteps($app, $info['repair-steps']['install']);
613
+
614
+        $config->setAppValue($app, 'installed_version', \OCP\Server::get(IAppManager::class)->getAppVersion($app));
615
+        if (array_key_exists('ocsid', $info)) {
616
+            $config->setAppValue($app, 'ocsid', $info['ocsid']);
617
+        }
618
+
619
+        //set remote/public handlers
620
+        foreach ($info['remote'] as $name => $path) {
621
+            $config->setAppValue('core', 'remote_' . $name, $app . '/' . $path);
622
+        }
623
+        foreach ($info['public'] as $name => $path) {
624
+            $config->setAppValue('core', 'public_' . $name, $app . '/' . $path);
625
+        }
626
+
627
+        OC_App::setAppTypes($info['id']);
628
+
629
+        return $info['id'];
630
+    }
631
+
632
+    private static function includeAppScript(string $script): void {
633
+        if (file_exists($script)) {
634
+            include $script;
635
+        }
636
+    }
637
+
638
+    /**
639
+     * Recursive copying of local folders.
640
+     *
641
+     * @param string $src source folder
642
+     * @param string $dest target folder
643
+     */
644
+    private function copyRecursive(string $src, string $dest): void {
645
+        if (!file_exists($src)) {
646
+            return;
647
+        }
648
+
649
+        if (is_dir($src)) {
650
+            if (!is_dir($dest)) {
651
+                mkdir($dest);
652
+            }
653
+            $files = scandir($src);
654
+            foreach ($files as $file) {
655
+                if ($file != '.' && $file != '..') {
656
+                    $this->copyRecursive("$src/$file", "$dest/$file");
657
+                }
658
+            }
659
+        } else {
660
+            $validator = Server::get(FilenameValidator::class);
661
+            if (!$validator->isForbidden($src)) {
662
+                copy($src, $dest);
663
+            }
664
+        }
665
+    }
666 666
 }
Please login to merge, or discard this patch.
Spacing   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -61,14 +61,14 @@  discard block
 block discarded – undo
61 61
 			throw new \Exception('App not found in any app directory');
62 62
 		}
63 63
 
64
-		$basedir = $app['path'] . '/' . $appId;
64
+		$basedir = $app['path'].'/'.$appId;
65 65
 
66
-		if (is_file($basedir . '/appinfo/database.xml')) {
67
-			throw new \Exception('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
66
+		if (is_file($basedir.'/appinfo/database.xml')) {
67
+			throw new \Exception('The appinfo/database.xml file is not longer supported. Used in '.$appId);
68 68
 		}
69 69
 
70 70
 		$l = \OCP\Util::getL10N('core');
71
-		$info = \OCP\Server::get(IAppManager::class)->getAppInfoByPath($basedir . '/appinfo/info.xml', $l->getLanguageCode());
71
+		$info = \OCP\Server::get(IAppManager::class)->getAppInfoByPath($basedir.'/appinfo/info.xml', $l->getLanguageCode());
72 72
 
73 73
 		if (!is_array($info)) {
74 74
 			throw new \Exception(
@@ -114,7 +114,7 @@  discard block
 block discarded – undo
114 114
 		\OC_App::setupBackgroundJobs($info['background-jobs']);
115 115
 
116 116
 		//run appinfo/install.php
117
-		self::includeAppScript($basedir . '/appinfo/install.php');
117
+		self::includeAppScript($basedir.'/appinfo/install.php');
118 118
 
119 119
 		OC_App::executeRepairSteps($appId, $info['repair-steps']['install']);
120 120
 
@@ -125,10 +125,10 @@  discard block
 block discarded – undo
125 125
 
126 126
 		//set remote/public handlers
127 127
 		foreach ($info['remote'] as $name => $path) {
128
-			$config->setAppValue('core', 'remote_' . $name, $info['id'] . '/' . $path);
128
+			$config->setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
129 129
 		}
130 130
 		foreach ($info['public'] as $name => $path) {
131
-			$config->setAppValue('core', 'public_' . $name, $info['id'] . '/' . $path);
131
+			$config->setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
132 132
 		}
133 133
 
134 134
 		OC_App::setAppTypes($info['id']);
@@ -213,7 +213,7 @@  discard block
 block discarded – undo
213 213
 			if ($app['id'] === $appId) {
214 214
 				// Load the certificate
215 215
 				$certificate = new X509();
216
-				$rootCrt = file_get_contents(__DIR__ . '/../../resources/codesigning/root.crt');
216
+				$rootCrt = file_get_contents(__DIR__.'/../../resources/codesigning/root.crt');
217 217
 				$rootCrts = $this->splitCerts($rootCrt);
218 218
 				foreach ($rootCrts as $rootCrt) {
219 219
 					$certificate->loadCA($rootCrt);
@@ -225,7 +225,7 @@  discard block
 block discarded – undo
225 225
 				foreach ($rootCrts as $rootCrt) {
226 226
 					$crl->loadCA($rootCrt);
227 227
 				}
228
-				$crl->loadCRL(file_get_contents(__DIR__ . '/../../resources/codesigning/root.crl'));
228
+				$crl->loadCRL(file_get_contents(__DIR__.'/../../resources/codesigning/root.crl'));
229 229
 				if ($crl->validateSignature() !== true) {
230 230
 					throw new \Exception('Could not validate CRL signature');
231 231
 				}
@@ -293,11 +293,11 @@  discard block
 block discarded – undo
293 293
 
294 294
 					$archive = new TAR($tempFile);
295 295
 					if (!$archive->extract($extractDir)) {
296
-						$errorMessage = 'Could not extract app ' . $appId;
296
+						$errorMessage = 'Could not extract app '.$appId;
297 297
 
298 298
 						$archiveError = $archive->getError();
299 299
 						if ($archiveError instanceof \PEAR_Error) {
300
-							$errorMessage .= ': ' . $archiveError->getMessage();
300
+							$errorMessage .= ': '.$archiveError->getMessage();
301 301
 						}
302 302
 
303 303
 						throw new \Exception($errorMessage);
@@ -325,7 +325,7 @@  discard block
 block discarded – undo
325 325
 					}
326 326
 
327 327
 					// Check if appinfo/info.xml has the same app ID as well
328
-					$xml = simplexml_load_string(file_get_contents($extractDir . '/' . $folders[0] . '/appinfo/info.xml'));
328
+					$xml = simplexml_load_string(file_get_contents($extractDir.'/'.$folders[0].'/appinfo/info.xml'));
329 329
 
330 330
 					if ($xml === false) {
331 331
 						throw new \Exception(
@@ -336,19 +336,19 @@  discard block
 block discarded – undo
336 336
 						);
337 337
 					}
338 338
 
339
-					if ((string)$xml->id !== $appId) {
339
+					if ((string) $xml->id !== $appId) {
340 340
 						throw new \Exception(
341 341
 							sprintf(
342 342
 								'App for id %s has a wrong app ID in info.xml: %s',
343 343
 								$appId,
344
-								(string)$xml->id
344
+								(string) $xml->id
345 345
 							)
346 346
 						);
347 347
 					}
348 348
 
349 349
 					// Check if the version is lower than before
350 350
 					$currentVersion = \OCP\Server::get(IAppManager::class)->getAppVersion($appId, true);
351
-					$newVersion = (string)$xml->version;
351
+					$newVersion = (string) $xml->version;
352 352
 					if (version_compare($currentVersion, $newVersion) === 1) {
353 353
 						throw new \Exception(
354 354
 							sprintf(
@@ -360,12 +360,12 @@  discard block
 block discarded – undo
360 360
 						);
361 361
 					}
362 362
 
363
-					$baseDir = $installPath . '/' . $appId;
363
+					$baseDir = $installPath.'/'.$appId;
364 364
 					// Remove old app with the ID if existent
365 365
 					Files::rmdirr($baseDir);
366 366
 					// Move to app folder
367 367
 					if (@mkdir($baseDir)) {
368
-						$extractDir .= '/' . $folders[0];
368
+						$extractDir .= '/'.$folders[0];
369 369
 					}
370 370
 					// otherwise we just copy the outer directory
371 371
 					$this->copyRecursive($extractDir, $baseDir);
@@ -400,7 +400,7 @@  discard block
 block discarded – undo
400 400
 	 * @param bool $allowUnstable
401 401
 	 * @return string|false false or the version number of the update
402 402
 	 */
403
-	public function isUpdateAvailable($appId, $allowUnstable = false): string|false {
403
+	public function isUpdateAvailable($appId, $allowUnstable = false): string | false {
404 404
 		if ($this->isInstanceReadyForUpdates === null) {
405 405
 			$installPath = $this->getInstallPath();
406 406
 			if ($installPath === null) {
@@ -451,8 +451,8 @@  discard block
 block discarded – undo
451 451
 		if ($app === false) {
452 452
 			return false;
453 453
 		}
454
-		$basedir = $app['path'] . '/' . $appId;
455
-		return file_exists($basedir . '/.git/');
454
+		$basedir = $app['path'].'/'.$appId;
455
+		return file_exists($basedir.'/.git/');
456 456
 	}
457 457
 
458 458
 	/**
@@ -496,11 +496,11 @@  discard block
 block discarded – undo
496 496
 				$this->logger->error('No application directories are marked as writable.', ['app' => 'core']);
497 497
 				return false;
498 498
 			}
499
-			$appDir = $installPath . '/' . $appId;
499
+			$appDir = $installPath.'/'.$appId;
500 500
 			Files::rmdirr($appDir);
501 501
 			return true;
502 502
 		} else {
503
-			$this->logger->error('can\'t remove app ' . $appId . '. It is not installed.');
503
+			$this->logger->error('can\'t remove app '.$appId.'. It is not installed.');
504 504
 
505 505
 			return false;
506 506
 		}
@@ -544,8 +544,8 @@  discard block
 block discarded – undo
544 544
 		foreach (\OC::$APPSROOTS as $app_dir) {
545 545
 			if ($dir = opendir($app_dir['path'])) {
546 546
 				while (false !== ($filename = readdir($dir))) {
547
-					if ($filename[0] !== '.' and is_dir($app_dir['path'] . "/$filename")) {
548
-						if (file_exists($app_dir['path'] . "/$filename/appinfo/info.xml")) {
547
+					if ($filename[0] !== '.' and is_dir($app_dir['path']."/$filename")) {
548
+						if (file_exists($app_dir['path']."/$filename/appinfo/info.xml")) {
549 549
 							if ($config->getAppValue($filename, 'installed_version', null) === null) {
550 550
 								$enabled = $appManager->isDefaultEnabled($filename);
551 551
 								if (($enabled || in_array($filename, $appManager->getAlwaysEnabledApps()))
@@ -579,9 +579,9 @@  discard block
 block discarded – undo
579 579
 	/**
580 580
 	 * install an app already placed in the app folder
581 581
 	 */
582
-	public static function installShippedApp(string $app, ?IOutput $output = null): string|false {
582
+	public static function installShippedApp(string $app, ?IOutput $output = null): string | false {
583 583
 		if ($output instanceof IOutput) {
584
-			$output->debug('Installing ' . $app);
584
+			$output->debug('Installing '.$app);
585 585
 		}
586 586
 
587 587
 		$appManager = \OCP\Server::get(IAppManager::class);
@@ -605,7 +605,7 @@  discard block
 block discarded – undo
605 605
 			return false;
606 606
 		}
607 607
 		if ($output instanceof IOutput) {
608
-			$output->debug('Registering tasks of ' . $app);
608
+			$output->debug('Registering tasks of '.$app);
609 609
 		}
610 610
 		\OC_App::setupBackgroundJobs($info['background-jobs']);
611 611
 
@@ -618,10 +618,10 @@  discard block
 block discarded – undo
618 618
 
619 619
 		//set remote/public handlers
620 620
 		foreach ($info['remote'] as $name => $path) {
621
-			$config->setAppValue('core', 'remote_' . $name, $app . '/' . $path);
621
+			$config->setAppValue('core', 'remote_'.$name, $app.'/'.$path);
622 622
 		}
623 623
 		foreach ($info['public'] as $name => $path) {
624
-			$config->setAppValue('core', 'public_' . $name, $app . '/' . $path);
624
+			$config->setAppValue('core', 'public_'.$name, $app.'/'.$path);
625 625
 		}
626 626
 
627 627
 		OC_App::setAppTypes($info['id']);
Please login to merge, or discard this patch.
lib/private/legacy/OC_Util.php 1 patch
Indentation   +822 added lines, -822 removed lines patch added patch discarded remove patch
@@ -22,826 +22,826 @@
 block discarded – undo
22 22
  * @deprecated 32.0.0 Use \OCP\Util or any appropriate official API instead
23 23
  */
24 24
 class OC_Util {
25
-	public static $styles = [];
26
-	public static $headers = [];
27
-
28
-	/**
29
-	 * Setup the file system
30
-	 *
31
-	 * @param string|null $user
32
-	 * @return boolean
33
-	 * @description configure the initial filesystem based on the configuration
34
-	 * @suppress PhanDeprecatedFunction
35
-	 * @suppress PhanAccessMethodInternal
36
-	 */
37
-	public static function setupFS(?string $user = '') {
38
-		// If we are not forced to load a specific user we load the one that is logged in
39
-		if ($user === '') {
40
-			$userObject = \OC::$server->get(\OCP\IUserSession::class)->getUser();
41
-		} else {
42
-			$userObject = \OC::$server->get(\OCP\IUserManager::class)->get($user);
43
-		}
44
-
45
-		/** @var SetupManager $setupManager */
46
-		$setupManager = \OC::$server->get(SetupManager::class);
47
-
48
-		if ($userObject) {
49
-			$setupManager->setupForUser($userObject);
50
-		} else {
51
-			$setupManager->setupRoot();
52
-		}
53
-		return true;
54
-	}
55
-
56
-	/**
57
-	 * Check if a password is required for each public link
58
-	 *
59
-	 * @param bool $checkGroupMembership Check group membership exclusion
60
-	 * @return bool
61
-	 * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkEnforcePassword directly
62
-	 */
63
-	public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) {
64
-		/** @var IManager $shareManager */
65
-		$shareManager = \OC::$server->get(IManager::class);
66
-		return $shareManager->shareApiLinkEnforcePassword($checkGroupMembership);
67
-	}
68
-
69
-	/**
70
-	 * check if sharing is disabled for the current user
71
-	 * @param IConfig $config
72
-	 * @param IGroupManager $groupManager
73
-	 * @param IUser|null $user
74
-	 * @return bool
75
-	 * @deprecated 32.0.0 use OCP\Share\IManager's sharingDisabledForUser directly
76
-	 */
77
-	public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
78
-		/** @var IManager $shareManager */
79
-		$shareManager = \OC::$server->get(IManager::class);
80
-		$userId = $user ? $user->getUID() : null;
81
-		return $shareManager->sharingDisabledForUser($userId);
82
-	}
83
-
84
-	/**
85
-	 * check if share API enforces a default expire date
86
-	 *
87
-	 * @return bool
88
-	 * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkDefaultExpireDateEnforced directly
89
-	 */
90
-	public static function isDefaultExpireDateEnforced() {
91
-		/** @var IManager $shareManager */
92
-		$shareManager = \OC::$server->get(IManager::class);
93
-		return $shareManager->shareApiLinkDefaultExpireDateEnforced();
94
-	}
95
-
96
-	/**
97
-	 * Get the quota of a user
98
-	 *
99
-	 * @param IUser|null $user
100
-	 * @return int|\OCP\Files\FileInfo::SPACE_UNLIMITED|false|float Quota bytes
101
-	 * @deprecated 9.0.0 - Use \OCP\IUser::getQuota or \OCP\IUser::getQuotaBytes
102
-	 */
103
-	public static function getUserQuota(?IUser $user) {
104
-		if (is_null($user)) {
105
-			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
106
-		}
107
-		$userQuota = $user->getQuota();
108
-		if ($userQuota === 'none') {
109
-			return \OCP\Files\FileInfo::SPACE_UNLIMITED;
110
-		}
111
-		return \OCP\Util::computerFileSize($userQuota);
112
-	}
113
-
114
-	/**
115
-	 * copies the skeleton to the users /files
116
-	 *
117
-	 * @param string $userId
118
-	 * @param \OCP\Files\Folder $userDirectory
119
-	 * @throws \OCP\Files\NotFoundException
120
-	 * @throws \OCP\Files\NotPermittedException
121
-	 * @suppress PhanDeprecatedFunction
122
-	 */
123
-	public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
124
-		/** @var LoggerInterface $logger */
125
-		$logger = \OC::$server->get(LoggerInterface::class);
126
-
127
-		$plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
128
-		$userLang = \OC::$server->get(IFactory::class)->findLanguage();
129
-		$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
130
-
131
-		if (!file_exists($skeletonDirectory)) {
132
-			$dialectStart = strpos($userLang, '_');
133
-			if ($dialectStart !== false) {
134
-				$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
135
-			}
136
-			if ($dialectStart === false || !file_exists($skeletonDirectory)) {
137
-				$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
138
-			}
139
-			if (!file_exists($skeletonDirectory)) {
140
-				$skeletonDirectory = '';
141
-			}
142
-		}
143
-
144
-		$instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
145
-
146
-		if ($instanceId === null) {
147
-			throw new \RuntimeException('no instance id!');
148
-		}
149
-		$appdata = 'appdata_' . $instanceId;
150
-		if ($userId === $appdata) {
151
-			throw new \RuntimeException('username is reserved name: ' . $appdata);
152
-		}
153
-
154
-		if (!empty($skeletonDirectory)) {
155
-			$logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
156
-			self::copyr($skeletonDirectory, $userDirectory);
157
-			// update the file cache
158
-			$userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
159
-
160
-			/** @var ITemplateManager $templateManager */
161
-			$templateManager = \OC::$server->get(ITemplateManager::class);
162
-			$templateManager->initializeTemplateDirectory(null, $userId);
163
-		}
164
-	}
165
-
166
-	/**
167
-	 * copies a directory recursively by using streams
168
-	 *
169
-	 * @param string $source
170
-	 * @param \OCP\Files\Folder $target
171
-	 * @return void
172
-	 */
173
-	public static function copyr($source, \OCP\Files\Folder $target) {
174
-		$logger = \OCP\Server::get(LoggerInterface::class);
175
-
176
-		// Verify if folder exists
177
-		$dir = opendir($source);
178
-		if ($dir === false) {
179
-			$logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
180
-			return;
181
-		}
182
-
183
-		// Copy the files
184
-		while (false !== ($file = readdir($dir))) {
185
-			if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
186
-				if (is_dir($source . '/' . $file)) {
187
-					$child = $target->newFolder($file);
188
-					self::copyr($source . '/' . $file, $child);
189
-				} else {
190
-					$sourceStream = fopen($source . '/' . $file, 'r');
191
-					if ($sourceStream === false) {
192
-						$logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
193
-						closedir($dir);
194
-						return;
195
-					}
196
-					$target->newFile($file, $sourceStream);
197
-				}
198
-			}
199
-		}
200
-		closedir($dir);
201
-	}
202
-
203
-	/**
204
-	 * @deprecated 32.0.0 Call tearDown directly on SetupManager
205
-	 */
206
-	public static function tearDownFS(): void {
207
-		$setupManager = \OCP\Server::get(SetupManager::class);
208
-		$setupManager->tearDown();
209
-	}
210
-
211
-	/**
212
-	 * generates a path for JS/CSS files. If no application is provided it will create the path for core.
213
-	 *
214
-	 * @param string $application application to get the files from
215
-	 * @param string $directory directory within this application (css, js, vendor, etc)
216
-	 * @param ?string $file the file inside of the above folder
217
-	 */
218
-	private static function generatePath($application, $directory, $file): string {
219
-		if (is_null($file)) {
220
-			$file = $application;
221
-			$application = '';
222
-		}
223
-		if (!empty($application)) {
224
-			return "$application/$directory/$file";
225
-		} else {
226
-			return "$directory/$file";
227
-		}
228
-	}
229
-
230
-	/**
231
-	 * add a css file
232
-	 *
233
-	 * @param string $application application id
234
-	 * @param string|null $file filename
235
-	 * @param bool $prepend prepend the Style to the beginning of the list
236
-	 * @deprecated 32.0.0 Use \OCP\Util::addStyle
237
-	 */
238
-	public static function addStyle($application, $file = null, $prepend = false): void {
239
-		$path = OC_Util::generatePath($application, 'css', $file);
240
-		self::addExternalResource($application, $prepend, $path, 'style');
241
-	}
242
-
243
-	/**
244
-	 * add a css file from the vendor sub folder
245
-	 *
246
-	 * @param string $application application id
247
-	 * @param string|null $file filename
248
-	 * @param bool $prepend prepend the Style to the beginning of the list
249
-	 * @deprecated 32.0.0
250
-	 */
251
-	public static function addVendorStyle($application, $file = null, $prepend = false): void {
252
-		$path = OC_Util::generatePath($application, 'vendor', $file);
253
-		self::addExternalResource($application, $prepend, $path, 'style');
254
-	}
255
-
256
-	/**
257
-	 * add an external resource css/js file
258
-	 *
259
-	 * @param string $application application id
260
-	 * @param bool $prepend prepend the file to the beginning of the list
261
-	 * @param string $path
262
-	 * @param string $type (script or style)
263
-	 */
264
-	private static function addExternalResource($application, $prepend, $path, $type = 'script'): void {
265
-		if ($type === 'style') {
266
-			if (!in_array($path, self::$styles)) {
267
-				if ($prepend === true) {
268
-					array_unshift(self::$styles, $path);
269
-				} else {
270
-					self::$styles[] = $path;
271
-				}
272
-			}
273
-		}
274
-	}
275
-
276
-	/**
277
-	 * Add a custom element to the header
278
-	 * If $text is null then the element will be written as empty element.
279
-	 * So use "" to get a closing tag.
280
-	 * @param string $tag tag name of the element
281
-	 * @param array $attributes array of attributes for the element
282
-	 * @param string $text the text content for the element
283
-	 * @param bool $prepend prepend the header to the beginning of the list
284
-	 * @deprecated 32.0.0 Use \OCP\Util::addHeader instead
285
-	 */
286
-	public static function addHeader($tag, $attributes, $text = null, $prepend = false): void {
287
-		$header = [
288
-			'tag' => $tag,
289
-			'attributes' => $attributes,
290
-			'text' => $text
291
-		];
292
-		if ($prepend === true) {
293
-			array_unshift(self::$headers, $header);
294
-		} else {
295
-			self::$headers[] = $header;
296
-		}
297
-	}
298
-
299
-	/**
300
-	 * check if the current server configuration is suitable for ownCloud
301
-	 *
302
-	 * @return array arrays with error messages and hints
303
-	 */
304
-	public static function checkServer(\OC\SystemConfig $config) {
305
-		$l = \OC::$server->getL10N('lib');
306
-		$errors = [];
307
-		$CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
308
-
309
-		if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
310
-			// this check needs to be done every time
311
-			$errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
312
-		}
313
-
314
-		// Assume that if checkServer() succeeded before in this session, then all is fine.
315
-		if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
316
-			return $errors;
317
-		}
318
-
319
-		$webServerRestart = false;
320
-		$setup = \OCP\Server::get(\OC\Setup::class);
321
-
322
-		$urlGenerator = \OC::$server->getURLGenerator();
323
-
324
-		$availableDatabases = $setup->getSupportedDatabases();
325
-		if (empty($availableDatabases)) {
326
-			$errors[] = [
327
-				'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
328
-				'hint' => '' //TODO: sane hint
329
-			];
330
-			$webServerRestart = true;
331
-		}
332
-
333
-		// Check if config folder is writable.
334
-		if (!(bool)$config->getValue('config_is_read_only', false)) {
335
-			if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
336
-				$errors[] = [
337
-					'error' => $l->t('Cannot write into "config" directory.'),
338
-					'hint' => $l->t('This can usually be fixed by giving the web server write access to the config directory. See %s',
339
-						[ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
340
-						. $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s',
341
-							[ $urlGenerator->linkToDocs('admin-config') ])
342
-				];
343
-			}
344
-		}
345
-
346
-		// Create root dir.
347
-		if ($config->getValue('installed', false)) {
348
-			if (!is_dir($CONFIG_DATADIRECTORY)) {
349
-				$success = @mkdir($CONFIG_DATADIRECTORY);
350
-				if ($success) {
351
-					$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
352
-				} else {
353
-					$errors[] = [
354
-						'error' => $l->t('Cannot create "data" directory.'),
355
-						'hint' => $l->t('This can usually be fixed by giving the web server write access to the root directory. See %s',
356
-							[$urlGenerator->linkToDocs('admin-dir_permissions')])
357
-					];
358
-				}
359
-			} elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
360
-				// is_writable doesn't work for NFS mounts, so try to write a file and check if it exists.
361
-				$testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_'));
362
-				$handle = fopen($testFile, 'w');
363
-				if (!$handle || fwrite($handle, 'Test write operation') === false) {
364
-					$permissionsHint = $l->t('Permissions can usually be fixed by giving the web server write access to the root directory. See %s.',
365
-						[$urlGenerator->linkToDocs('admin-dir_permissions')]);
366
-					$errors[] = [
367
-						'error' => $l->t('Your data directory is not writable.'),
368
-						'hint' => $permissionsHint
369
-					];
370
-				} else {
371
-					fclose($handle);
372
-					unlink($testFile);
373
-				}
374
-			} else {
375
-				$errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
376
-			}
377
-		}
378
-
379
-		if (!OC_Util::isSetLocaleWorking()) {
380
-			$errors[] = [
381
-				'error' => $l->t('Setting locale to %s failed.',
382
-					['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
383
-						. 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']),
384
-				'hint' => $l->t('Please install one of these locales on your system and restart your web server.')
385
-			];
386
-		}
387
-
388
-		// Contains the dependencies that should be checked against
389
-		// classes = class_exists
390
-		// functions = function_exists
391
-		// defined = defined
392
-		// ini = ini_get
393
-		// If the dependency is not found the missing module name is shown to the EndUser
394
-		// When adding new checks always verify that they pass on CI as well
395
-		$dependencies = [
396
-			'classes' => [
397
-				'ZipArchive' => 'zip',
398
-				'DOMDocument' => 'dom',
399
-				'XMLWriter' => 'XMLWriter',
400
-				'XMLReader' => 'XMLReader',
401
-			],
402
-			'functions' => [
403
-				'xml_parser_create' => 'libxml',
404
-				'mb_strcut' => 'mbstring',
405
-				'ctype_digit' => 'ctype',
406
-				'json_encode' => 'JSON',
407
-				'gd_info' => 'GD',
408
-				'gzencode' => 'zlib',
409
-				'simplexml_load_string' => 'SimpleXML',
410
-				'hash' => 'HASH Message Digest Framework',
411
-				'curl_init' => 'cURL',
412
-				'openssl_verify' => 'OpenSSL',
413
-			],
414
-			'defined' => [
415
-				'PDO::ATTR_DRIVER_NAME' => 'PDO'
416
-			],
417
-			'ini' => [
418
-				'default_charset' => 'UTF-8',
419
-			],
420
-		];
421
-		$missingDependencies = [];
422
-		$invalidIniSettings = [];
423
-
424
-		$iniWrapper = \OC::$server->get(IniGetWrapper::class);
425
-		foreach ($dependencies['classes'] as $class => $module) {
426
-			if (!class_exists($class)) {
427
-				$missingDependencies[] = $module;
428
-			}
429
-		}
430
-		foreach ($dependencies['functions'] as $function => $module) {
431
-			if (!function_exists($function)) {
432
-				$missingDependencies[] = $module;
433
-			}
434
-		}
435
-		foreach ($dependencies['defined'] as $defined => $module) {
436
-			if (!defined($defined)) {
437
-				$missingDependencies[] = $module;
438
-			}
439
-		}
440
-		foreach ($dependencies['ini'] as $setting => $expected) {
441
-			if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
442
-				$invalidIniSettings[] = [$setting, $expected];
443
-			}
444
-		}
445
-
446
-		foreach ($missingDependencies as $missingDependency) {
447
-			$errors[] = [
448
-				'error' => $l->t('PHP module %s not installed.', [$missingDependency]),
449
-				'hint' => $l->t('Please ask your server administrator to install the module.'),
450
-			];
451
-			$webServerRestart = true;
452
-		}
453
-		foreach ($invalidIniSettings as $setting) {
454
-			$errors[] = [
455
-				'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
456
-				'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
457
-			];
458
-			$webServerRestart = true;
459
-		}
460
-
461
-		/**
462
-		 * The mbstring.func_overload check can only be performed if the mbstring
463
-		 * module is installed as it will return null if the checking setting is
464
-		 * not available and thus a check on the boolean value fails.
465
-		 *
466
-		 * TODO: Should probably be implemented in the above generic dependency
467
-		 *       check somehow in the long-term.
468
-		 */
469
-		if ($iniWrapper->getBool('mbstring.func_overload') !== null
470
-			&& $iniWrapper->getBool('mbstring.func_overload') === true) {
471
-			$errors[] = [
472
-				'error' => $l->t('<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>.', [$iniWrapper->getString('mbstring.func_overload')]),
473
-				'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini.')
474
-			];
475
-		}
476
-
477
-		if (!self::isAnnotationsWorking()) {
478
-			$errors[] = [
479
-				'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
480
-				'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
481
-			];
482
-		}
483
-
484
-		if (!\OC::$CLI && $webServerRestart) {
485
-			$errors[] = [
486
-				'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
487
-				'hint' => $l->t('Please ask your server administrator to restart the web server.')
488
-			];
489
-		}
490
-
491
-		foreach (['secret', 'instanceid', 'passwordsalt'] as $requiredConfig) {
492
-			if ($config->getValue($requiredConfig, '') === '' && !\OC::$CLI && $config->getValue('installed', false)) {
493
-				$errors[] = [
494
-					'error' => $l->t('The required %s config variable is not configured in the config.php file.', [$requiredConfig]),
495
-					'hint' => $l->t('Please ask your server administrator to check the Nextcloud configuration.')
496
-				];
497
-			}
498
-		}
499
-
500
-		// Cache the result of this function
501
-		\OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
502
-
503
-		return $errors;
504
-	}
505
-
506
-	/**
507
-	 * Check for correct file permissions of data directory
508
-	 *
509
-	 * @param string $dataDirectory
510
-	 * @return array arrays with error messages and hints
511
-	 * @internal
512
-	 */
513
-	public static function checkDataDirectoryPermissions($dataDirectory) {
514
-		if (!\OC::$server->getConfig()->getSystemValueBool('check_data_directory_permissions', true)) {
515
-			return  [];
516
-		}
517
-
518
-		$perms = substr(decoct(@fileperms($dataDirectory)), -3);
519
-		if (substr($perms, -1) !== '0') {
520
-			chmod($dataDirectory, 0770);
521
-			clearstatcache();
522
-			$perms = substr(decoct(@fileperms($dataDirectory)), -3);
523
-			if ($perms[2] !== '0') {
524
-				$l = \OC::$server->getL10N('lib');
525
-				return [[
526
-					'error' => $l->t('Your data directory is readable by other people.'),
527
-					'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other people.'),
528
-				]];
529
-			}
530
-		}
531
-		return [];
532
-	}
533
-
534
-	/**
535
-	 * Check that the data directory exists and is valid by
536
-	 * checking the existence of the ".ncdata" file.
537
-	 *
538
-	 * @param string $dataDirectory data directory path
539
-	 * @return array errors found
540
-	 * @internal
541
-	 */
542
-	public static function checkDataDirectoryValidity($dataDirectory) {
543
-		$l = \OC::$server->getL10N('lib');
544
-		$errors = [];
545
-		if ($dataDirectory[0] !== '/') {
546
-			$errors[] = [
547
-				'error' => $l->t('Your data directory must be an absolute path.'),
548
-				'hint' => $l->t('Check the value of "datadirectory" in your configuration.')
549
-			];
550
-		}
551
-
552
-		if (!file_exists($dataDirectory . '/.ncdata')) {
553
-			$errors[] = [
554
-				'error' => $l->t('Your data directory is invalid.'),
555
-				'hint' => $l->t('Ensure there is a file called "%1$s" in the root of the data directory. It should have the content: "%2$s"', ['.ncdata', '# Nextcloud data directory']),
556
-			];
557
-		}
558
-		return $errors;
559
-	}
560
-
561
-	/**
562
-	 * Check if the user is logged in, redirects to home if not. With
563
-	 * redirect URL parameter to the request URI.
564
-	 *
565
-	 * @deprecated 32.0.0
566
-	 */
567
-	public static function checkLoggedIn(): void {
568
-		// Check if we are a user
569
-		if (!\OC::$server->getUserSession()->isLoggedIn()) {
570
-			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
571
-				'core.login.showLoginForm',
572
-				[
573
-					'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
574
-				]
575
-			)
576
-			);
577
-			exit();
578
-		}
579
-		// Redirect to 2FA challenge selection if 2FA challenge was not solved yet
580
-		if (\OC::$server->get(TwoFactorAuthManager::class)->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
581
-			header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
582
-			exit();
583
-		}
584
-	}
585
-
586
-	/**
587
-	 * Check if the user is a admin, redirects to home if not
588
-	 *
589
-	 * @deprecated 32.0.0
590
-	 */
591
-	public static function checkAdminUser(): void {
592
-		self::checkLoggedIn();
593
-		if (!OC_User::isAdminUser(OC_User::getUser())) {
594
-			header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
595
-			exit();
596
-		}
597
-	}
598
-
599
-	/**
600
-	 * Returns the URL of the default page
601
-	 * based on the system configuration and
602
-	 * the apps visible for the current user
603
-	 *
604
-	 * @return string URL
605
-	 * @deprecated 32.0.0 use IURLGenerator's linkToDefaultPageUrl directly
606
-	 */
607
-	public static function getDefaultPageUrl() {
608
-		/** @var IURLGenerator $urlGenerator */
609
-		$urlGenerator = \OC::$server->get(IURLGenerator::class);
610
-		return $urlGenerator->linkToDefaultPageUrl();
611
-	}
612
-
613
-	/**
614
-	 * Redirect to the user default page
615
-	 *
616
-	 * @deprecated 32.0.0
617
-	 */
618
-	public static function redirectToDefaultPage(): void {
619
-		$location = self::getDefaultPageUrl();
620
-		header('Location: ' . $location);
621
-		exit();
622
-	}
623
-
624
-	/**
625
-	 * get an id unique for this instance
626
-	 *
627
-	 * @return string
628
-	 */
629
-	public static function getInstanceId(): string {
630
-		$id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
631
-		if (is_null($id)) {
632
-			// We need to guarantee at least one letter in instanceid so it can be used as the session_name
633
-			$id = 'oc' . \OC::$server->get(ISecureRandom::class)->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_DIGITS);
634
-			\OC::$server->getSystemConfig()->setValue('instanceid', $id);
635
-		}
636
-		return $id;
637
-	}
638
-
639
-	/**
640
-	 * Public function to sanitize HTML
641
-	 *
642
-	 * This function is used to sanitize HTML and should be applied on any
643
-	 * string or array of strings before displaying it on a web page.
644
-	 *
645
-	 * @param string|string[] $value
646
-	 * @return ($value is array ? string[] : string)
647
-	 * @deprecated 32.0.0 use \OCP\Util::sanitizeHTML instead
648
-	 */
649
-	public static function sanitizeHTML($value) {
650
-		if (is_array($value)) {
651
-			$value = array_map(function ($value) {
652
-				return self::sanitizeHTML($value);
653
-			}, $value);
654
-		} else {
655
-			// Specify encoding for PHP<5.4
656
-			$value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
657
-		}
658
-		return $value;
659
-	}
660
-
661
-	/**
662
-	 * Public function to encode url parameters
663
-	 *
664
-	 * This function is used to encode path to file before output.
665
-	 * Encoding is done according to RFC 3986 with one exception:
666
-	 * Character '/' is preserved as is.
667
-	 *
668
-	 * @param string $component part of URI to encode
669
-	 * @return string
670
-	 * @deprecated 32.0.0 use \OCP\Util::encodePath instead
671
-	 */
672
-	public static function encodePath($component) {
673
-		$encoded = rawurlencode($component);
674
-		$encoded = str_replace('%2F', '/', $encoded);
675
-		return $encoded;
676
-	}
677
-
678
-	/**
679
-	 * Check if current locale is non-UTF8
680
-	 *
681
-	 * @return bool
682
-	 */
683
-	private static function isNonUTF8Locale() {
684
-		if (function_exists('escapeshellcmd')) {
685
-			return escapeshellcmd('§') === '';
686
-		} elseif (function_exists('escapeshellarg')) {
687
-			return escapeshellarg('§') === '\'\'';
688
-		} else {
689
-			return preg_match('/utf-?8/i', setlocale(LC_CTYPE, 0)) === 0;
690
-		}
691
-	}
692
-
693
-	/**
694
-	 * Check if the setlocale call does not work. This can happen if the right
695
-	 * local packages are not available on the server.
696
-	 *
697
-	 * @internal
698
-	 */
699
-	public static function isSetLocaleWorking(): bool {
700
-		if (self::isNonUTF8Locale()) {
701
-			// Borrowed from \Patchwork\Utf8\Bootup::initLocale
702
-			setlocale(LC_ALL, 'C.UTF-8', 'C');
703
-			setlocale(LC_CTYPE, 'en_US.UTF-8', 'fr_FR.UTF-8', 'es_ES.UTF-8', 'de_DE.UTF-8', 'ru_RU.UTF-8', 'pt_BR.UTF-8', 'it_IT.UTF-8', 'ja_JP.UTF-8', 'zh_CN.UTF-8', '0');
704
-
705
-			// Check again
706
-			if (self::isNonUTF8Locale()) {
707
-				return false;
708
-			}
709
-		}
710
-
711
-		return true;
712
-	}
713
-
714
-	/**
715
-	 * Check if it's possible to get the inline annotations
716
-	 *
717
-	 * @internal
718
-	 */
719
-	public static function isAnnotationsWorking(): bool {
720
-		if (PHP_VERSION_ID >= 80300) {
721
-			/** @psalm-suppress UndefinedMethod */
722
-			$reflection = \ReflectionMethod::createFromMethodName(__METHOD__);
723
-		} else {
724
-			$reflection = new \ReflectionMethod(__METHOD__);
725
-		}
726
-		$docs = $reflection->getDocComment();
727
-
728
-		return (is_string($docs) && strlen($docs) > 50);
729
-	}
730
-
731
-	/**
732
-	 * Check if the PHP module fileinfo is loaded.
733
-	 *
734
-	 * @internal
735
-	 */
736
-	public static function fileInfoLoaded(): bool {
737
-		return function_exists('finfo_open');
738
-	}
739
-
740
-	/**
741
-	 * clear all levels of output buffering
742
-	 *
743
-	 * @return void
744
-	 */
745
-	public static function obEnd() {
746
-		while (ob_get_level()) {
747
-			ob_end_clean();
748
-		}
749
-	}
750
-
751
-	/**
752
-	 * Checks whether the server is running on Mac OS X
753
-	 *
754
-	 * @return bool true if running on Mac OS X, false otherwise
755
-	 */
756
-	public static function runningOnMac() {
757
-		return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
758
-	}
759
-
760
-	/**
761
-	 * Handles the case that there may not be a theme, then check if a "default"
762
-	 * theme exists and take that one
763
-	 *
764
-	 * @return string the theme
765
-	 */
766
-	public static function getTheme() {
767
-		$theme = \OC::$server->getSystemConfig()->getValue('theme', '');
768
-
769
-		if ($theme === '') {
770
-			if (is_dir(OC::$SERVERROOT . '/themes/default')) {
771
-				$theme = 'default';
772
-			}
773
-		}
774
-
775
-		return $theme;
776
-	}
777
-
778
-	/**
779
-	 * Normalize a unicode string
780
-	 *
781
-	 * @param string $value a not normalized string
782
-	 * @return string The normalized string or the input if the normalization failed
783
-	 */
784
-	public static function normalizeUnicode(string $value): string {
785
-		if (Normalizer::isNormalized($value)) {
786
-			return $value;
787
-		}
788
-
789
-		$normalizedValue = Normalizer::normalize($value);
790
-		if ($normalizedValue === false) {
791
-			\OCP\Server::get(LoggerInterface::class)->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
792
-			return $value;
793
-		}
794
-
795
-		return $normalizedValue;
796
-	}
797
-
798
-	/**
799
-	 * Check whether the instance needs to perform an upgrade,
800
-	 * either when the core version is higher or any app requires
801
-	 * an upgrade.
802
-	 *
803
-	 * @param \OC\SystemConfig $config
804
-	 * @return bool whether the core or any app needs an upgrade
805
-	 * @throws \OCP\HintException When the upgrade from the given version is not allowed
806
-	 * @deprecated 32.0.0 Use \OCP\Util::needUpgrade instead
807
-	 */
808
-	public static function needUpgrade(\OC\SystemConfig $config) {
809
-		if ($config->getValue('installed', false)) {
810
-			$installedVersion = $config->getValue('version', '0.0.0');
811
-			$currentVersion = implode('.', \OCP\Util::getVersion());
812
-			$versionDiff = version_compare($currentVersion, $installedVersion);
813
-			if ($versionDiff > 0) {
814
-				return true;
815
-			} elseif ($config->getValue('debug', false) && $versionDiff < 0) {
816
-				// downgrade with debug
817
-				$installedMajor = explode('.', $installedVersion);
818
-				$installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
819
-				$currentMajor = explode('.', $currentVersion);
820
-				$currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
821
-				if ($installedMajor === $currentMajor) {
822
-					// Same major, allow downgrade for developers
823
-					return true;
824
-				} else {
825
-					// downgrade attempt, throw exception
826
-					throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
827
-				}
828
-			} elseif ($versionDiff < 0) {
829
-				// downgrade attempt, throw exception
830
-				throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
831
-			}
832
-
833
-			// also check for upgrades for apps (independently from the user)
834
-			$apps = \OC_App::getEnabledApps(false, true);
835
-			$shouldUpgrade = false;
836
-			foreach ($apps as $app) {
837
-				if (\OC_App::shouldUpgrade($app)) {
838
-					$shouldUpgrade = true;
839
-					break;
840
-				}
841
-			}
842
-			return $shouldUpgrade;
843
-		} else {
844
-			return false;
845
-		}
846
-	}
25
+    public static $styles = [];
26
+    public static $headers = [];
27
+
28
+    /**
29
+     * Setup the file system
30
+     *
31
+     * @param string|null $user
32
+     * @return boolean
33
+     * @description configure the initial filesystem based on the configuration
34
+     * @suppress PhanDeprecatedFunction
35
+     * @suppress PhanAccessMethodInternal
36
+     */
37
+    public static function setupFS(?string $user = '') {
38
+        // If we are not forced to load a specific user we load the one that is logged in
39
+        if ($user === '') {
40
+            $userObject = \OC::$server->get(\OCP\IUserSession::class)->getUser();
41
+        } else {
42
+            $userObject = \OC::$server->get(\OCP\IUserManager::class)->get($user);
43
+        }
44
+
45
+        /** @var SetupManager $setupManager */
46
+        $setupManager = \OC::$server->get(SetupManager::class);
47
+
48
+        if ($userObject) {
49
+            $setupManager->setupForUser($userObject);
50
+        } else {
51
+            $setupManager->setupRoot();
52
+        }
53
+        return true;
54
+    }
55
+
56
+    /**
57
+     * Check if a password is required for each public link
58
+     *
59
+     * @param bool $checkGroupMembership Check group membership exclusion
60
+     * @return bool
61
+     * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkEnforcePassword directly
62
+     */
63
+    public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) {
64
+        /** @var IManager $shareManager */
65
+        $shareManager = \OC::$server->get(IManager::class);
66
+        return $shareManager->shareApiLinkEnforcePassword($checkGroupMembership);
67
+    }
68
+
69
+    /**
70
+     * check if sharing is disabled for the current user
71
+     * @param IConfig $config
72
+     * @param IGroupManager $groupManager
73
+     * @param IUser|null $user
74
+     * @return bool
75
+     * @deprecated 32.0.0 use OCP\Share\IManager's sharingDisabledForUser directly
76
+     */
77
+    public static function isSharingDisabledForUser(IConfig $config, IGroupManager $groupManager, $user) {
78
+        /** @var IManager $shareManager */
79
+        $shareManager = \OC::$server->get(IManager::class);
80
+        $userId = $user ? $user->getUID() : null;
81
+        return $shareManager->sharingDisabledForUser($userId);
82
+    }
83
+
84
+    /**
85
+     * check if share API enforces a default expire date
86
+     *
87
+     * @return bool
88
+     * @deprecated 32.0.0 use OCP\Share\IManager's shareApiLinkDefaultExpireDateEnforced directly
89
+     */
90
+    public static function isDefaultExpireDateEnforced() {
91
+        /** @var IManager $shareManager */
92
+        $shareManager = \OC::$server->get(IManager::class);
93
+        return $shareManager->shareApiLinkDefaultExpireDateEnforced();
94
+    }
95
+
96
+    /**
97
+     * Get the quota of a user
98
+     *
99
+     * @param IUser|null $user
100
+     * @return int|\OCP\Files\FileInfo::SPACE_UNLIMITED|false|float Quota bytes
101
+     * @deprecated 9.0.0 - Use \OCP\IUser::getQuota or \OCP\IUser::getQuotaBytes
102
+     */
103
+    public static function getUserQuota(?IUser $user) {
104
+        if (is_null($user)) {
105
+            return \OCP\Files\FileInfo::SPACE_UNLIMITED;
106
+        }
107
+        $userQuota = $user->getQuota();
108
+        if ($userQuota === 'none') {
109
+            return \OCP\Files\FileInfo::SPACE_UNLIMITED;
110
+        }
111
+        return \OCP\Util::computerFileSize($userQuota);
112
+    }
113
+
114
+    /**
115
+     * copies the skeleton to the users /files
116
+     *
117
+     * @param string $userId
118
+     * @param \OCP\Files\Folder $userDirectory
119
+     * @throws \OCP\Files\NotFoundException
120
+     * @throws \OCP\Files\NotPermittedException
121
+     * @suppress PhanDeprecatedFunction
122
+     */
123
+    public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
124
+        /** @var LoggerInterface $logger */
125
+        $logger = \OC::$server->get(LoggerInterface::class);
126
+
127
+        $plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
128
+        $userLang = \OC::$server->get(IFactory::class)->findLanguage();
129
+        $skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
130
+
131
+        if (!file_exists($skeletonDirectory)) {
132
+            $dialectStart = strpos($userLang, '_');
133
+            if ($dialectStart !== false) {
134
+                $skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
135
+            }
136
+            if ($dialectStart === false || !file_exists($skeletonDirectory)) {
137
+                $skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
138
+            }
139
+            if (!file_exists($skeletonDirectory)) {
140
+                $skeletonDirectory = '';
141
+            }
142
+        }
143
+
144
+        $instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', '');
145
+
146
+        if ($instanceId === null) {
147
+            throw new \RuntimeException('no instance id!');
148
+        }
149
+        $appdata = 'appdata_' . $instanceId;
150
+        if ($userId === $appdata) {
151
+            throw new \RuntimeException('username is reserved name: ' . $appdata);
152
+        }
153
+
154
+        if (!empty($skeletonDirectory)) {
155
+            $logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
156
+            self::copyr($skeletonDirectory, $userDirectory);
157
+            // update the file cache
158
+            $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
159
+
160
+            /** @var ITemplateManager $templateManager */
161
+            $templateManager = \OC::$server->get(ITemplateManager::class);
162
+            $templateManager->initializeTemplateDirectory(null, $userId);
163
+        }
164
+    }
165
+
166
+    /**
167
+     * copies a directory recursively by using streams
168
+     *
169
+     * @param string $source
170
+     * @param \OCP\Files\Folder $target
171
+     * @return void
172
+     */
173
+    public static function copyr($source, \OCP\Files\Folder $target) {
174
+        $logger = \OCP\Server::get(LoggerInterface::class);
175
+
176
+        // Verify if folder exists
177
+        $dir = opendir($source);
178
+        if ($dir === false) {
179
+            $logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
180
+            return;
181
+        }
182
+
183
+        // Copy the files
184
+        while (false !== ($file = readdir($dir))) {
185
+            if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
186
+                if (is_dir($source . '/' . $file)) {
187
+                    $child = $target->newFolder($file);
188
+                    self::copyr($source . '/' . $file, $child);
189
+                } else {
190
+                    $sourceStream = fopen($source . '/' . $file, 'r');
191
+                    if ($sourceStream === false) {
192
+                        $logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
193
+                        closedir($dir);
194
+                        return;
195
+                    }
196
+                    $target->newFile($file, $sourceStream);
197
+                }
198
+            }
199
+        }
200
+        closedir($dir);
201
+    }
202
+
203
+    /**
204
+     * @deprecated 32.0.0 Call tearDown directly on SetupManager
205
+     */
206
+    public static function tearDownFS(): void {
207
+        $setupManager = \OCP\Server::get(SetupManager::class);
208
+        $setupManager->tearDown();
209
+    }
210
+
211
+    /**
212
+     * generates a path for JS/CSS files. If no application is provided it will create the path for core.
213
+     *
214
+     * @param string $application application to get the files from
215
+     * @param string $directory directory within this application (css, js, vendor, etc)
216
+     * @param ?string $file the file inside of the above folder
217
+     */
218
+    private static function generatePath($application, $directory, $file): string {
219
+        if (is_null($file)) {
220
+            $file = $application;
221
+            $application = '';
222
+        }
223
+        if (!empty($application)) {
224
+            return "$application/$directory/$file";
225
+        } else {
226
+            return "$directory/$file";
227
+        }
228
+    }
229
+
230
+    /**
231
+     * add a css file
232
+     *
233
+     * @param string $application application id
234
+     * @param string|null $file filename
235
+     * @param bool $prepend prepend the Style to the beginning of the list
236
+     * @deprecated 32.0.0 Use \OCP\Util::addStyle
237
+     */
238
+    public static function addStyle($application, $file = null, $prepend = false): void {
239
+        $path = OC_Util::generatePath($application, 'css', $file);
240
+        self::addExternalResource($application, $prepend, $path, 'style');
241
+    }
242
+
243
+    /**
244
+     * add a css file from the vendor sub folder
245
+     *
246
+     * @param string $application application id
247
+     * @param string|null $file filename
248
+     * @param bool $prepend prepend the Style to the beginning of the list
249
+     * @deprecated 32.0.0
250
+     */
251
+    public static function addVendorStyle($application, $file = null, $prepend = false): void {
252
+        $path = OC_Util::generatePath($application, 'vendor', $file);
253
+        self::addExternalResource($application, $prepend, $path, 'style');
254
+    }
255
+
256
+    /**
257
+     * add an external resource css/js file
258
+     *
259
+     * @param string $application application id
260
+     * @param bool $prepend prepend the file to the beginning of the list
261
+     * @param string $path
262
+     * @param string $type (script or style)
263
+     */
264
+    private static function addExternalResource($application, $prepend, $path, $type = 'script'): void {
265
+        if ($type === 'style') {
266
+            if (!in_array($path, self::$styles)) {
267
+                if ($prepend === true) {
268
+                    array_unshift(self::$styles, $path);
269
+                } else {
270
+                    self::$styles[] = $path;
271
+                }
272
+            }
273
+        }
274
+    }
275
+
276
+    /**
277
+     * Add a custom element to the header
278
+     * If $text is null then the element will be written as empty element.
279
+     * So use "" to get a closing tag.
280
+     * @param string $tag tag name of the element
281
+     * @param array $attributes array of attributes for the element
282
+     * @param string $text the text content for the element
283
+     * @param bool $prepend prepend the header to the beginning of the list
284
+     * @deprecated 32.0.0 Use \OCP\Util::addHeader instead
285
+     */
286
+    public static function addHeader($tag, $attributes, $text = null, $prepend = false): void {
287
+        $header = [
288
+            'tag' => $tag,
289
+            'attributes' => $attributes,
290
+            'text' => $text
291
+        ];
292
+        if ($prepend === true) {
293
+            array_unshift(self::$headers, $header);
294
+        } else {
295
+            self::$headers[] = $header;
296
+        }
297
+    }
298
+
299
+    /**
300
+     * check if the current server configuration is suitable for ownCloud
301
+     *
302
+     * @return array arrays with error messages and hints
303
+     */
304
+    public static function checkServer(\OC\SystemConfig $config) {
305
+        $l = \OC::$server->getL10N('lib');
306
+        $errors = [];
307
+        $CONFIG_DATADIRECTORY = $config->getValue('datadirectory', OC::$SERVERROOT . '/data');
308
+
309
+        if (!self::needUpgrade($config) && $config->getValue('installed', false)) {
310
+            // this check needs to be done every time
311
+            $errors = self::checkDataDirectoryValidity($CONFIG_DATADIRECTORY);
312
+        }
313
+
314
+        // Assume that if checkServer() succeeded before in this session, then all is fine.
315
+        if (\OC::$server->getSession()->exists('checkServer_succeeded') && \OC::$server->getSession()->get('checkServer_succeeded')) {
316
+            return $errors;
317
+        }
318
+
319
+        $webServerRestart = false;
320
+        $setup = \OCP\Server::get(\OC\Setup::class);
321
+
322
+        $urlGenerator = \OC::$server->getURLGenerator();
323
+
324
+        $availableDatabases = $setup->getSupportedDatabases();
325
+        if (empty($availableDatabases)) {
326
+            $errors[] = [
327
+                'error' => $l->t('No database drivers (sqlite, mysql, or postgresql) installed.'),
328
+                'hint' => '' //TODO: sane hint
329
+            ];
330
+            $webServerRestart = true;
331
+        }
332
+
333
+        // Check if config folder is writable.
334
+        if (!(bool)$config->getValue('config_is_read_only', false)) {
335
+            if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) {
336
+                $errors[] = [
337
+                    'error' => $l->t('Cannot write into "config" directory.'),
338
+                    'hint' => $l->t('This can usually be fixed by giving the web server write access to the config directory. See %s',
339
+                        [ $urlGenerator->linkToDocs('admin-dir_permissions') ]) . '. '
340
+                        . $l->t('Or, if you prefer to keep config.php file read only, set the option "config_is_read_only" to true in it. See %s',
341
+                            [ $urlGenerator->linkToDocs('admin-config') ])
342
+                ];
343
+            }
344
+        }
345
+
346
+        // Create root dir.
347
+        if ($config->getValue('installed', false)) {
348
+            if (!is_dir($CONFIG_DATADIRECTORY)) {
349
+                $success = @mkdir($CONFIG_DATADIRECTORY);
350
+                if ($success) {
351
+                    $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
352
+                } else {
353
+                    $errors[] = [
354
+                        'error' => $l->t('Cannot create "data" directory.'),
355
+                        'hint' => $l->t('This can usually be fixed by giving the web server write access to the root directory. See %s',
356
+                            [$urlGenerator->linkToDocs('admin-dir_permissions')])
357
+                    ];
358
+                }
359
+            } elseif (!is_writable($CONFIG_DATADIRECTORY) or !is_readable($CONFIG_DATADIRECTORY)) {
360
+                // is_writable doesn't work for NFS mounts, so try to write a file and check if it exists.
361
+                $testFile = sprintf('%s/%s.tmp', $CONFIG_DATADIRECTORY, uniqid('data_dir_writability_test_'));
362
+                $handle = fopen($testFile, 'w');
363
+                if (!$handle || fwrite($handle, 'Test write operation') === false) {
364
+                    $permissionsHint = $l->t('Permissions can usually be fixed by giving the web server write access to the root directory. See %s.',
365
+                        [$urlGenerator->linkToDocs('admin-dir_permissions')]);
366
+                    $errors[] = [
367
+                        'error' => $l->t('Your data directory is not writable.'),
368
+                        'hint' => $permissionsHint
369
+                    ];
370
+                } else {
371
+                    fclose($handle);
372
+                    unlink($testFile);
373
+                }
374
+            } else {
375
+                $errors = array_merge($errors, self::checkDataDirectoryPermissions($CONFIG_DATADIRECTORY));
376
+            }
377
+        }
378
+
379
+        if (!OC_Util::isSetLocaleWorking()) {
380
+            $errors[] = [
381
+                'error' => $l->t('Setting locale to %s failed.',
382
+                    ['en_US.UTF-8/fr_FR.UTF-8/es_ES.UTF-8/de_DE.UTF-8/ru_RU.UTF-8/'
383
+                        . 'pt_BR.UTF-8/it_IT.UTF-8/ja_JP.UTF-8/zh_CN.UTF-8']),
384
+                'hint' => $l->t('Please install one of these locales on your system and restart your web server.')
385
+            ];
386
+        }
387
+
388
+        // Contains the dependencies that should be checked against
389
+        // classes = class_exists
390
+        // functions = function_exists
391
+        // defined = defined
392
+        // ini = ini_get
393
+        // If the dependency is not found the missing module name is shown to the EndUser
394
+        // When adding new checks always verify that they pass on CI as well
395
+        $dependencies = [
396
+            'classes' => [
397
+                'ZipArchive' => 'zip',
398
+                'DOMDocument' => 'dom',
399
+                'XMLWriter' => 'XMLWriter',
400
+                'XMLReader' => 'XMLReader',
401
+            ],
402
+            'functions' => [
403
+                'xml_parser_create' => 'libxml',
404
+                'mb_strcut' => 'mbstring',
405
+                'ctype_digit' => 'ctype',
406
+                'json_encode' => 'JSON',
407
+                'gd_info' => 'GD',
408
+                'gzencode' => 'zlib',
409
+                'simplexml_load_string' => 'SimpleXML',
410
+                'hash' => 'HASH Message Digest Framework',
411
+                'curl_init' => 'cURL',
412
+                'openssl_verify' => 'OpenSSL',
413
+            ],
414
+            'defined' => [
415
+                'PDO::ATTR_DRIVER_NAME' => 'PDO'
416
+            ],
417
+            'ini' => [
418
+                'default_charset' => 'UTF-8',
419
+            ],
420
+        ];
421
+        $missingDependencies = [];
422
+        $invalidIniSettings = [];
423
+
424
+        $iniWrapper = \OC::$server->get(IniGetWrapper::class);
425
+        foreach ($dependencies['classes'] as $class => $module) {
426
+            if (!class_exists($class)) {
427
+                $missingDependencies[] = $module;
428
+            }
429
+        }
430
+        foreach ($dependencies['functions'] as $function => $module) {
431
+            if (!function_exists($function)) {
432
+                $missingDependencies[] = $module;
433
+            }
434
+        }
435
+        foreach ($dependencies['defined'] as $defined => $module) {
436
+            if (!defined($defined)) {
437
+                $missingDependencies[] = $module;
438
+            }
439
+        }
440
+        foreach ($dependencies['ini'] as $setting => $expected) {
441
+            if (strtolower($iniWrapper->getString($setting)) !== strtolower($expected)) {
442
+                $invalidIniSettings[] = [$setting, $expected];
443
+            }
444
+        }
445
+
446
+        foreach ($missingDependencies as $missingDependency) {
447
+            $errors[] = [
448
+                'error' => $l->t('PHP module %s not installed.', [$missingDependency]),
449
+                'hint' => $l->t('Please ask your server administrator to install the module.'),
450
+            ];
451
+            $webServerRestart = true;
452
+        }
453
+        foreach ($invalidIniSettings as $setting) {
454
+            $errors[] = [
455
+                'error' => $l->t('PHP setting "%s" is not set to "%s".', [$setting[0], var_export($setting[1], true)]),
456
+                'hint' => $l->t('Adjusting this setting in php.ini will make Nextcloud run again')
457
+            ];
458
+            $webServerRestart = true;
459
+        }
460
+
461
+        /**
462
+         * The mbstring.func_overload check can only be performed if the mbstring
463
+         * module is installed as it will return null if the checking setting is
464
+         * not available and thus a check on the boolean value fails.
465
+         *
466
+         * TODO: Should probably be implemented in the above generic dependency
467
+         *       check somehow in the long-term.
468
+         */
469
+        if ($iniWrapper->getBool('mbstring.func_overload') !== null
470
+            && $iniWrapper->getBool('mbstring.func_overload') === true) {
471
+            $errors[] = [
472
+                'error' => $l->t('<code>mbstring.func_overload</code> is set to <code>%s</code> instead of the expected value <code>0</code>.', [$iniWrapper->getString('mbstring.func_overload')]),
473
+                'hint' => $l->t('To fix this issue set <code>mbstring.func_overload</code> to <code>0</code> in your php.ini.')
474
+            ];
475
+        }
476
+
477
+        if (!self::isAnnotationsWorking()) {
478
+            $errors[] = [
479
+                'error' => $l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.'),
480
+                'hint' => $l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')
481
+            ];
482
+        }
483
+
484
+        if (!\OC::$CLI && $webServerRestart) {
485
+            $errors[] = [
486
+                'error' => $l->t('PHP modules have been installed, but they are still listed as missing?'),
487
+                'hint' => $l->t('Please ask your server administrator to restart the web server.')
488
+            ];
489
+        }
490
+
491
+        foreach (['secret', 'instanceid', 'passwordsalt'] as $requiredConfig) {
492
+            if ($config->getValue($requiredConfig, '') === '' && !\OC::$CLI && $config->getValue('installed', false)) {
493
+                $errors[] = [
494
+                    'error' => $l->t('The required %s config variable is not configured in the config.php file.', [$requiredConfig]),
495
+                    'hint' => $l->t('Please ask your server administrator to check the Nextcloud configuration.')
496
+                ];
497
+            }
498
+        }
499
+
500
+        // Cache the result of this function
501
+        \OC::$server->getSession()->set('checkServer_succeeded', count($errors) == 0);
502
+
503
+        return $errors;
504
+    }
505
+
506
+    /**
507
+     * Check for correct file permissions of data directory
508
+     *
509
+     * @param string $dataDirectory
510
+     * @return array arrays with error messages and hints
511
+     * @internal
512
+     */
513
+    public static function checkDataDirectoryPermissions($dataDirectory) {
514
+        if (!\OC::$server->getConfig()->getSystemValueBool('check_data_directory_permissions', true)) {
515
+            return  [];
516
+        }
517
+
518
+        $perms = substr(decoct(@fileperms($dataDirectory)), -3);
519
+        if (substr($perms, -1) !== '0') {
520
+            chmod($dataDirectory, 0770);
521
+            clearstatcache();
522
+            $perms = substr(decoct(@fileperms($dataDirectory)), -3);
523
+            if ($perms[2] !== '0') {
524
+                $l = \OC::$server->getL10N('lib');
525
+                return [[
526
+                    'error' => $l->t('Your data directory is readable by other people.'),
527
+                    'hint' => $l->t('Please change the permissions to 0770 so that the directory cannot be listed by other people.'),
528
+                ]];
529
+            }
530
+        }
531
+        return [];
532
+    }
533
+
534
+    /**
535
+     * Check that the data directory exists and is valid by
536
+     * checking the existence of the ".ncdata" file.
537
+     *
538
+     * @param string $dataDirectory data directory path
539
+     * @return array errors found
540
+     * @internal
541
+     */
542
+    public static function checkDataDirectoryValidity($dataDirectory) {
543
+        $l = \OC::$server->getL10N('lib');
544
+        $errors = [];
545
+        if ($dataDirectory[0] !== '/') {
546
+            $errors[] = [
547
+                'error' => $l->t('Your data directory must be an absolute path.'),
548
+                'hint' => $l->t('Check the value of "datadirectory" in your configuration.')
549
+            ];
550
+        }
551
+
552
+        if (!file_exists($dataDirectory . '/.ncdata')) {
553
+            $errors[] = [
554
+                'error' => $l->t('Your data directory is invalid.'),
555
+                'hint' => $l->t('Ensure there is a file called "%1$s" in the root of the data directory. It should have the content: "%2$s"', ['.ncdata', '# Nextcloud data directory']),
556
+            ];
557
+        }
558
+        return $errors;
559
+    }
560
+
561
+    /**
562
+     * Check if the user is logged in, redirects to home if not. With
563
+     * redirect URL parameter to the request URI.
564
+     *
565
+     * @deprecated 32.0.0
566
+     */
567
+    public static function checkLoggedIn(): void {
568
+        // Check if we are a user
569
+        if (!\OC::$server->getUserSession()->isLoggedIn()) {
570
+            header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute(
571
+                'core.login.showLoginForm',
572
+                [
573
+                    'redirect_url' => \OC::$server->getRequest()->getRequestUri(),
574
+                ]
575
+            )
576
+            );
577
+            exit();
578
+        }
579
+        // Redirect to 2FA challenge selection if 2FA challenge was not solved yet
580
+        if (\OC::$server->get(TwoFactorAuthManager::class)->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
581
+            header('Location: ' . \OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
582
+            exit();
583
+        }
584
+    }
585
+
586
+    /**
587
+     * Check if the user is a admin, redirects to home if not
588
+     *
589
+     * @deprecated 32.0.0
590
+     */
591
+    public static function checkAdminUser(): void {
592
+        self::checkLoggedIn();
593
+        if (!OC_User::isAdminUser(OC_User::getUser())) {
594
+            header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php'));
595
+            exit();
596
+        }
597
+    }
598
+
599
+    /**
600
+     * Returns the URL of the default page
601
+     * based on the system configuration and
602
+     * the apps visible for the current user
603
+     *
604
+     * @return string URL
605
+     * @deprecated 32.0.0 use IURLGenerator's linkToDefaultPageUrl directly
606
+     */
607
+    public static function getDefaultPageUrl() {
608
+        /** @var IURLGenerator $urlGenerator */
609
+        $urlGenerator = \OC::$server->get(IURLGenerator::class);
610
+        return $urlGenerator->linkToDefaultPageUrl();
611
+    }
612
+
613
+    /**
614
+     * Redirect to the user default page
615
+     *
616
+     * @deprecated 32.0.0
617
+     */
618
+    public static function redirectToDefaultPage(): void {
619
+        $location = self::getDefaultPageUrl();
620
+        header('Location: ' . $location);
621
+        exit();
622
+    }
623
+
624
+    /**
625
+     * get an id unique for this instance
626
+     *
627
+     * @return string
628
+     */
629
+    public static function getInstanceId(): string {
630
+        $id = \OC::$server->getSystemConfig()->getValue('instanceid', null);
631
+        if (is_null($id)) {
632
+            // We need to guarantee at least one letter in instanceid so it can be used as the session_name
633
+            $id = 'oc' . \OC::$server->get(ISecureRandom::class)->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_DIGITS);
634
+            \OC::$server->getSystemConfig()->setValue('instanceid', $id);
635
+        }
636
+        return $id;
637
+    }
638
+
639
+    /**
640
+     * Public function to sanitize HTML
641
+     *
642
+     * This function is used to sanitize HTML and should be applied on any
643
+     * string or array of strings before displaying it on a web page.
644
+     *
645
+     * @param string|string[] $value
646
+     * @return ($value is array ? string[] : string)
647
+     * @deprecated 32.0.0 use \OCP\Util::sanitizeHTML instead
648
+     */
649
+    public static function sanitizeHTML($value) {
650
+        if (is_array($value)) {
651
+            $value = array_map(function ($value) {
652
+                return self::sanitizeHTML($value);
653
+            }, $value);
654
+        } else {
655
+            // Specify encoding for PHP<5.4
656
+            $value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
657
+        }
658
+        return $value;
659
+    }
660
+
661
+    /**
662
+     * Public function to encode url parameters
663
+     *
664
+     * This function is used to encode path to file before output.
665
+     * Encoding is done according to RFC 3986 with one exception:
666
+     * Character '/' is preserved as is.
667
+     *
668
+     * @param string $component part of URI to encode
669
+     * @return string
670
+     * @deprecated 32.0.0 use \OCP\Util::encodePath instead
671
+     */
672
+    public static function encodePath($component) {
673
+        $encoded = rawurlencode($component);
674
+        $encoded = str_replace('%2F', '/', $encoded);
675
+        return $encoded;
676
+    }
677
+
678
+    /**
679
+     * Check if current locale is non-UTF8
680
+     *
681
+     * @return bool
682
+     */
683
+    private static function isNonUTF8Locale() {
684
+        if (function_exists('escapeshellcmd')) {
685
+            return escapeshellcmd('§') === '';
686
+        } elseif (function_exists('escapeshellarg')) {
687
+            return escapeshellarg('§') === '\'\'';
688
+        } else {
689
+            return preg_match('/utf-?8/i', setlocale(LC_CTYPE, 0)) === 0;
690
+        }
691
+    }
692
+
693
+    /**
694
+     * Check if the setlocale call does not work. This can happen if the right
695
+     * local packages are not available on the server.
696
+     *
697
+     * @internal
698
+     */
699
+    public static function isSetLocaleWorking(): bool {
700
+        if (self::isNonUTF8Locale()) {
701
+            // Borrowed from \Patchwork\Utf8\Bootup::initLocale
702
+            setlocale(LC_ALL, 'C.UTF-8', 'C');
703
+            setlocale(LC_CTYPE, 'en_US.UTF-8', 'fr_FR.UTF-8', 'es_ES.UTF-8', 'de_DE.UTF-8', 'ru_RU.UTF-8', 'pt_BR.UTF-8', 'it_IT.UTF-8', 'ja_JP.UTF-8', 'zh_CN.UTF-8', '0');
704
+
705
+            // Check again
706
+            if (self::isNonUTF8Locale()) {
707
+                return false;
708
+            }
709
+        }
710
+
711
+        return true;
712
+    }
713
+
714
+    /**
715
+     * Check if it's possible to get the inline annotations
716
+     *
717
+     * @internal
718
+     */
719
+    public static function isAnnotationsWorking(): bool {
720
+        if (PHP_VERSION_ID >= 80300) {
721
+            /** @psalm-suppress UndefinedMethod */
722
+            $reflection = \ReflectionMethod::createFromMethodName(__METHOD__);
723
+        } else {
724
+            $reflection = new \ReflectionMethod(__METHOD__);
725
+        }
726
+        $docs = $reflection->getDocComment();
727
+
728
+        return (is_string($docs) && strlen($docs) > 50);
729
+    }
730
+
731
+    /**
732
+     * Check if the PHP module fileinfo is loaded.
733
+     *
734
+     * @internal
735
+     */
736
+    public static function fileInfoLoaded(): bool {
737
+        return function_exists('finfo_open');
738
+    }
739
+
740
+    /**
741
+     * clear all levels of output buffering
742
+     *
743
+     * @return void
744
+     */
745
+    public static function obEnd() {
746
+        while (ob_get_level()) {
747
+            ob_end_clean();
748
+        }
749
+    }
750
+
751
+    /**
752
+     * Checks whether the server is running on Mac OS X
753
+     *
754
+     * @return bool true if running on Mac OS X, false otherwise
755
+     */
756
+    public static function runningOnMac() {
757
+        return (strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN');
758
+    }
759
+
760
+    /**
761
+     * Handles the case that there may not be a theme, then check if a "default"
762
+     * theme exists and take that one
763
+     *
764
+     * @return string the theme
765
+     */
766
+    public static function getTheme() {
767
+        $theme = \OC::$server->getSystemConfig()->getValue('theme', '');
768
+
769
+        if ($theme === '') {
770
+            if (is_dir(OC::$SERVERROOT . '/themes/default')) {
771
+                $theme = 'default';
772
+            }
773
+        }
774
+
775
+        return $theme;
776
+    }
777
+
778
+    /**
779
+     * Normalize a unicode string
780
+     *
781
+     * @param string $value a not normalized string
782
+     * @return string The normalized string or the input if the normalization failed
783
+     */
784
+    public static function normalizeUnicode(string $value): string {
785
+        if (Normalizer::isNormalized($value)) {
786
+            return $value;
787
+        }
788
+
789
+        $normalizedValue = Normalizer::normalize($value);
790
+        if ($normalizedValue === false) {
791
+            \OCP\Server::get(LoggerInterface::class)->warning('normalizing failed for "' . $value . '"', ['app' => 'core']);
792
+            return $value;
793
+        }
794
+
795
+        return $normalizedValue;
796
+    }
797
+
798
+    /**
799
+     * Check whether the instance needs to perform an upgrade,
800
+     * either when the core version is higher or any app requires
801
+     * an upgrade.
802
+     *
803
+     * @param \OC\SystemConfig $config
804
+     * @return bool whether the core or any app needs an upgrade
805
+     * @throws \OCP\HintException When the upgrade from the given version is not allowed
806
+     * @deprecated 32.0.0 Use \OCP\Util::needUpgrade instead
807
+     */
808
+    public static function needUpgrade(\OC\SystemConfig $config) {
809
+        if ($config->getValue('installed', false)) {
810
+            $installedVersion = $config->getValue('version', '0.0.0');
811
+            $currentVersion = implode('.', \OCP\Util::getVersion());
812
+            $versionDiff = version_compare($currentVersion, $installedVersion);
813
+            if ($versionDiff > 0) {
814
+                return true;
815
+            } elseif ($config->getValue('debug', false) && $versionDiff < 0) {
816
+                // downgrade with debug
817
+                $installedMajor = explode('.', $installedVersion);
818
+                $installedMajor = $installedMajor[0] . '.' . $installedMajor[1];
819
+                $currentMajor = explode('.', $currentVersion);
820
+                $currentMajor = $currentMajor[0] . '.' . $currentMajor[1];
821
+                if ($installedMajor === $currentMajor) {
822
+                    // Same major, allow downgrade for developers
823
+                    return true;
824
+                } else {
825
+                    // downgrade attempt, throw exception
826
+                    throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
827
+                }
828
+            } elseif ($versionDiff < 0) {
829
+                // downgrade attempt, throw exception
830
+                throw new \OCP\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')');
831
+            }
832
+
833
+            // also check for upgrades for apps (independently from the user)
834
+            $apps = \OC_App::getEnabledApps(false, true);
835
+            $shouldUpgrade = false;
836
+            foreach ($apps as $app) {
837
+                if (\OC_App::shouldUpgrade($app)) {
838
+                    $shouldUpgrade = true;
839
+                    break;
840
+                }
841
+            }
842
+            return $shouldUpgrade;
843
+        } else {
844
+            return false;
845
+        }
846
+    }
847 847
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_App.php 1 patch
Indentation   +747 added lines, -747 removed lines patch added patch discarded remove patch
@@ -31,751 +31,751 @@
 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
-	 * Find the apps root for an app id.
242
-	 *
243
-	 * If multiple copies are found, the apps root the latest version is returned.
244
-	 *
245
-	 * @param string $appId
246
-	 * @param bool $ignoreCache ignore cache and rebuild it
247
-	 * @return false|array{path: string, url: string} the apps root shape
248
-	 */
249
-	public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
250
-		$sanitizedAppId = self::cleanAppId($appId);
251
-		if ($sanitizedAppId !== $appId) {
252
-			return false;
253
-		}
254
-		static $app_dir = [];
255
-
256
-		if (isset($app_dir[$appId]) && !$ignoreCache) {
257
-			return $app_dir[$appId];
258
-		}
259
-
260
-		$possibleApps = [];
261
-		foreach (OC::$APPSROOTS as $dir) {
262
-			if (file_exists($dir['path'] . '/' . $appId)) {
263
-				$possibleApps[] = $dir;
264
-			}
265
-		}
266
-
267
-		if (empty($possibleApps)) {
268
-			return false;
269
-		} elseif (count($possibleApps) === 1) {
270
-			$dir = array_shift($possibleApps);
271
-			$app_dir[$appId] = $dir;
272
-			return $dir;
273
-		} else {
274
-			$versionToLoad = [];
275
-			foreach ($possibleApps as $possibleApp) {
276
-				$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
277
-				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
278
-					$versionToLoad = [
279
-						'dir' => $possibleApp,
280
-						'version' => $version,
281
-					];
282
-				}
283
-			}
284
-			$app_dir[$appId] = $versionToLoad['dir'];
285
-			return $versionToLoad['dir'];
286
-			//TODO - write test
287
-		}
288
-	}
289
-
290
-	/**
291
-	 * Get the directory for the given app.
292
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
293
-	 *
294
-	 * @psalm-taint-specialize
295
-	 *
296
-	 * @param string $appId
297
-	 * @param bool $refreshAppPath should be set to true only during install/upgrade
298
-	 * @return string|false
299
-	 * @deprecated 11.0.0 use Server::get(IAppManager)->getAppPath()
300
-	 */
301
-	public static function getAppPath(string $appId, bool $refreshAppPath = false) {
302
-		$appId = self::cleanAppId($appId);
303
-		if ($appId === '') {
304
-			return false;
305
-		} elseif ($appId === 'core') {
306
-			return __DIR__ . '/../../../core';
307
-		}
308
-
309
-		if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
310
-			return $dir['path'] . '/' . $appId;
311
-		}
312
-		return false;
313
-	}
314
-
315
-	/**
316
-	 * Get the path for the given app on the access
317
-	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
318
-	 *
319
-	 * @param string $appId
320
-	 * @return string|false
321
-	 * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
322
-	 */
323
-	public static function getAppWebPath(string $appId) {
324
-		if (($dir = self::findAppInDirectories($appId)) != false) {
325
-			return OC::$WEBROOT . $dir['url'] . '/' . $appId;
326
-		}
327
-		return false;
328
-	}
329
-
330
-	/**
331
-	 * get app's version based on it's path
332
-	 *
333
-	 * @param string $path
334
-	 * @return string
335
-	 */
336
-	public static function getAppVersionByPath(string $path): string {
337
-		$infoFile = $path . '/appinfo/info.xml';
338
-		$appData = Server::get(IAppManager::class)->getAppInfoByPath($infoFile);
339
-		return $appData['version'] ?? '';
340
-	}
341
-
342
-	/**
343
-	 * get the id of loaded app
344
-	 *
345
-	 * @return string
346
-	 */
347
-	public static function getCurrentApp(): string {
348
-		if (\OC::$CLI) {
349
-			return '';
350
-		}
351
-
352
-		$request = \OC::$server->getRequest();
353
-		$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
354
-		$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
355
-		if (empty($topFolder)) {
356
-			try {
357
-				$path_info = $request->getPathInfo();
358
-			} catch (Exception $e) {
359
-				// Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
360
-				\OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
361
-				return '';
362
-			}
363
-			if ($path_info) {
364
-				$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
365
-			}
366
-		}
367
-		if ($topFolder == 'apps') {
368
-			$length = strlen($topFolder);
369
-			return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
370
-		} else {
371
-			return $topFolder;
372
-		}
373
-	}
374
-
375
-	/**
376
-	 * @param array $entry
377
-	 * @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
378
-	 */
379
-	public static function registerLogIn(array $entry) {
380
-		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');
381
-		self::$altLogin[] = $entry;
382
-	}
383
-
384
-	/**
385
-	 * @return array
386
-	 */
387
-	public static function getAlternativeLogIns(): array {
388
-		/** @var Coordinator $bootstrapCoordinator */
389
-		$bootstrapCoordinator = Server::get(Coordinator::class);
390
-
391
-		foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
392
-			if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
393
-				Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
394
-					'option' => $registration->getService(),
395
-					'interface' => IAlternativeLogin::class,
396
-					'app' => $registration->getAppId(),
397
-				]);
398
-				continue;
399
-			}
400
-
401
-			try {
402
-				/** @var IAlternativeLogin $provider */
403
-				$provider = Server::get($registration->getService());
404
-			} catch (ContainerExceptionInterface $e) {
405
-				Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.',
406
-					[
407
-						'exception' => $e,
408
-						'option' => $registration->getService(),
409
-						'app' => $registration->getAppId(),
410
-					]);
411
-			}
412
-
413
-			try {
414
-				$provider->load();
415
-
416
-				self::$altLogin[] = [
417
-					'name' => $provider->getLabel(),
418
-					'href' => $provider->getLink(),
419
-					'class' => $provider->getClass(),
420
-				];
421
-			} catch (Throwable $e) {
422
-				Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.',
423
-					[
424
-						'exception' => $e,
425
-						'option' => $registration->getService(),
426
-						'app' => $registration->getAppId(),
427
-					]);
428
-			}
429
-		}
430
-
431
-		return self::$altLogin;
432
-	}
433
-
434
-	/**
435
-	 * get a list of all apps in the apps folder
436
-	 *
437
-	 * @return string[] an array of app names (string IDs)
438
-	 * @deprecated 31.0.0 Use IAppManager::getAllAppsInAppsFolders instead
439
-	 */
440
-	public static function getAllApps(): array {
441
-		return Server::get(IAppManager::class)->getAllAppsInAppsFolders();
442
-	}
443
-
444
-	/**
445
-	 * List all supported apps
446
-	 *
447
-	 * @deprecated 32.0.0 Use \OCP\Support\Subscription\IRegistry::delegateGetSupportedApps instead
448
-	 */
449
-	public function getSupportedApps(): array {
450
-		$subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
451
-		$supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
452
-		return $supportedApps;
453
-	}
454
-
455
-	/**
456
-	 * List all apps, this is used in apps.php
457
-	 *
458
-	 * @return array
459
-	 */
460
-	public function listAllApps(): array {
461
-		$appManager = \OC::$server->getAppManager();
462
-
463
-		$installedApps = $appManager->getAllAppsInAppsFolders();
464
-		//we don't want to show configuration for these
465
-		$blacklist = $appManager->getAlwaysEnabledApps();
466
-		$appList = [];
467
-		$langCode = \OC::$server->getL10N('core')->getLanguageCode();
468
-		$urlGenerator = \OC::$server->getURLGenerator();
469
-		$supportedApps = $this->getSupportedApps();
470
-
471
-		foreach ($installedApps as $app) {
472
-			if (!in_array($app, $blacklist)) {
473
-				$info = $appManager->getAppInfo($app, false, $langCode);
474
-				if (!is_array($info)) {
475
-					Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
476
-					continue;
477
-				}
478
-
479
-				if (!isset($info['name'])) {
480
-					Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
481
-					continue;
482
-				}
483
-
484
-				$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
485
-				$info['groups'] = null;
486
-				if ($enabled === 'yes') {
487
-					$active = true;
488
-				} elseif ($enabled === 'no') {
489
-					$active = false;
490
-				} else {
491
-					$active = true;
492
-					$info['groups'] = $enabled;
493
-				}
494
-
495
-				$info['active'] = $active;
496
-
497
-				if ($appManager->isShipped($app)) {
498
-					$info['internal'] = true;
499
-					$info['level'] = self::officialApp;
500
-					$info['removable'] = false;
501
-				} else {
502
-					$info['internal'] = false;
503
-					$info['removable'] = true;
504
-				}
505
-
506
-				if (in_array($app, $supportedApps)) {
507
-					$info['level'] = self::supportedApp;
508
-				}
509
-
510
-				$appPath = self::getAppPath($app);
511
-				if ($appPath !== false) {
512
-					$appIcon = $appPath . '/img/' . $app . '.svg';
513
-					if (file_exists($appIcon)) {
514
-						$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
515
-						$info['previewAsIcon'] = true;
516
-					} else {
517
-						$appIcon = $appPath . '/img/app.svg';
518
-						if (file_exists($appIcon)) {
519
-							$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
520
-							$info['previewAsIcon'] = true;
521
-						}
522
-					}
523
-				}
524
-				// fix documentation
525
-				if (isset($info['documentation']) && is_array($info['documentation'])) {
526
-					foreach ($info['documentation'] as $key => $url) {
527
-						// If it is not an absolute URL we assume it is a key
528
-						// i.e. admin-ldap will get converted to go.php?to=admin-ldap
529
-						if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
530
-							$url = $urlGenerator->linkToDocs($url);
531
-						}
532
-
533
-						$info['documentation'][$key] = $url;
534
-					}
535
-				}
536
-
537
-				$info['version'] = $appManager->getAppVersion($app);
538
-				$appList[] = $info;
539
-			}
540
-		}
541
-
542
-		return $appList;
543
-	}
544
-
545
-	public static function shouldUpgrade(string $app): bool {
546
-		$versions = self::getAppVersions();
547
-		$currentVersion = Server::get(\OCP\App\IAppManager::class)->getAppVersion($app);
548
-		if ($currentVersion && isset($versions[$app])) {
549
-			$installedVersion = $versions[$app];
550
-			if (!version_compare($currentVersion, $installedVersion, '=')) {
551
-				return true;
552
-			}
553
-		}
554
-		return false;
555
-	}
556
-
557
-	/**
558
-	 * Adjust the number of version parts of $version1 to match
559
-	 * the number of version parts of $version2.
560
-	 *
561
-	 * @param string $version1 version to adjust
562
-	 * @param string $version2 version to take the number of parts from
563
-	 * @return string shortened $version1
564
-	 */
565
-	private static function adjustVersionParts(string $version1, string $version2): string {
566
-		$version1 = explode('.', $version1);
567
-		$version2 = explode('.', $version2);
568
-		// reduce $version1 to match the number of parts in $version2
569
-		while (count($version1) > count($version2)) {
570
-			array_pop($version1);
571
-		}
572
-		// if $version1 does not have enough parts, add some
573
-		while (count($version1) < count($version2)) {
574
-			$version1[] = '0';
575
-		}
576
-		return implode('.', $version1);
577
-	}
578
-
579
-	/**
580
-	 * Check whether the current Nextcloud version matches the given
581
-	 * application's version requirements.
582
-	 *
583
-	 * The comparison is made based on the number of parts that the
584
-	 * app info version has. For example for ownCloud 6.0.3 if the
585
-	 * app info version is expecting version 6.0, the comparison is
586
-	 * made on the first two parts of the ownCloud version.
587
-	 * This means that it's possible to specify "requiremin" => 6
588
-	 * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
589
-	 *
590
-	 * @param string $ocVersion Nextcloud version to check against
591
-	 * @param array $appInfo app info (from xml)
592
-	 *
593
-	 * @return boolean true if compatible, otherwise false
594
-	 */
595
-	public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
596
-		$requireMin = '';
597
-		$requireMax = '';
598
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
599
-			$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
600
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
601
-			$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
602
-		} elseif (isset($appInfo['requiremin'])) {
603
-			$requireMin = $appInfo['requiremin'];
604
-		} elseif (isset($appInfo['require'])) {
605
-			$requireMin = $appInfo['require'];
606
-		}
607
-
608
-		if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
609
-			$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
610
-		} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
611
-			$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
612
-		} elseif (isset($appInfo['requiremax'])) {
613
-			$requireMax = $appInfo['requiremax'];
614
-		}
615
-
616
-		if (!empty($requireMin)
617
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
618
-		) {
619
-			return false;
620
-		}
621
-
622
-		if (!$ignoreMax && !empty($requireMax)
623
-			&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
624
-		) {
625
-			return false;
626
-		}
627
-
628
-		return true;
629
-	}
630
-
631
-	/**
632
-	 * get the installed version of all apps
633
-	 * @deprecated 32.0.0 Use IAppManager::getAppInstalledVersions or IAppConfig::getAppInstalledVersions instead
634
-	 */
635
-	public static function getAppVersions(): array {
636
-		return Server::get(IAppConfig::class)->getAppInstalledVersions();
637
-	}
638
-
639
-	/**
640
-	 * update the database for the app and call the update script
641
-	 *
642
-	 * @param string $appId
643
-	 * @return bool
644
-	 */
645
-	public static function updateApp(string $appId): bool {
646
-		// for apps distributed with core, we refresh app path in case the downloaded version
647
-		// have been installed in custom apps and not in the default path
648
-		$appPath = self::getAppPath($appId, true);
649
-		if ($appPath === false) {
650
-			return false;
651
-		}
652
-
653
-		if (is_file($appPath . '/appinfo/database.xml')) {
654
-			Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
655
-			return false;
656
-		}
657
-
658
-		\OC::$server->getAppManager()->clearAppsCache();
659
-		$l = \OC::$server->getL10N('core');
660
-		$appData = Server::get(\OCP\App\IAppManager::class)->getAppInfo($appId, false, $l->getLanguageCode());
661
-
662
-		$ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
663
-		$ignoreMax = in_array($appId, $ignoreMaxApps, true);
664
-		\OC_App::checkAppDependencies(
665
-			\OC::$server->getConfig(),
666
-			$l,
667
-			$appData,
668
-			$ignoreMax
669
-		);
670
-
671
-		self::registerAutoloading($appId, $appPath, true);
672
-		self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
673
-
674
-		$ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
675
-		$ms->migrate();
676
-
677
-		self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
678
-		self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
679
-		// update appversion in app manager
680
-		\OC::$server->getAppManager()->clearAppsCache();
681
-		\OC::$server->getAppManager()->getAppVersion($appId, false);
682
-
683
-		self::setupBackgroundJobs($appData['background-jobs']);
684
-
685
-		//set remote/public handlers
686
-		if (array_key_exists('ocsid', $appData)) {
687
-			\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
688
-		} elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
689
-			\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
690
-		}
691
-		foreach ($appData['remote'] as $name => $path) {
692
-			\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
693
-		}
694
-		foreach ($appData['public'] as $name => $path) {
695
-			\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
696
-		}
697
-
698
-		self::setAppTypes($appId);
699
-
700
-		$version = Server::get(\OCP\App\IAppManager::class)->getAppVersion($appId);
701
-		\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
702
-
703
-		// migrate eventual new config keys in the process
704
-		/** @psalm-suppress InternalMethod */
705
-		Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId);
706
-
707
-		\OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
708
-		\OC::$server->get(IEventDispatcher::class)->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
709
-			ManagerEvent::EVENT_APP_UPDATE, $appId
710
-		));
711
-
712
-		return true;
713
-	}
714
-
715
-	/**
716
-	 * @param string $appId
717
-	 * @param string[] $steps
718
-	 * @throws \OC\NeedsUpdateException
719
-	 */
720
-	public static function executeRepairSteps(string $appId, array $steps) {
721
-		if (empty($steps)) {
722
-			return;
723
-		}
724
-		// load the app
725
-		self::loadApp($appId);
726
-
727
-		$dispatcher = Server::get(IEventDispatcher::class);
728
-
729
-		// load the steps
730
-		$r = Server::get(Repair::class);
731
-		foreach ($steps as $step) {
732
-			try {
733
-				$r->addStep($step);
734
-			} catch (Exception $ex) {
735
-				$dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
736
-				logger('core')->error('Failed to add app migration step ' . $step, ['exception' => $ex]);
737
-			}
738
-		}
739
-		// run the steps
740
-		$r->run();
741
-	}
742
-
743
-	public static function setupBackgroundJobs(array $jobs) {
744
-		$queue = \OC::$server->getJobList();
745
-		foreach ($jobs as $job) {
746
-			$queue->add($job);
747
-		}
748
-	}
749
-
750
-	/**
751
-	 * @param string $appId
752
-	 * @param string[] $steps
753
-	 */
754
-	private static function setupLiveMigrations(string $appId, array $steps) {
755
-		$queue = \OC::$server->getJobList();
756
-		foreach ($steps as $step) {
757
-			$queue->add('OC\Migration\BackgroundRepair', [
758
-				'app' => $appId,
759
-				'step' => $step]);
760
-		}
761
-	}
762
-
763
-	/**
764
-	 * @param \OCP\IConfig $config
765
-	 * @param \OCP\IL10N $l
766
-	 * @param array $info
767
-	 * @throws \Exception
768
-	 */
769
-	public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
770
-		$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
771
-		$missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
772
-		if (!empty($missing)) {
773
-			$missingMsg = implode(PHP_EOL, $missing);
774
-			throw new \Exception(
775
-				$l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
776
-					[$info['name'], $missingMsg]
777
-				)
778
-			);
779
-		}
780
-	}
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
+     * Find the apps root for an app id.
242
+     *
243
+     * If multiple copies are found, the apps root the latest version is returned.
244
+     *
245
+     * @param string $appId
246
+     * @param bool $ignoreCache ignore cache and rebuild it
247
+     * @return false|array{path: string, url: string} the apps root shape
248
+     */
249
+    public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
250
+        $sanitizedAppId = self::cleanAppId($appId);
251
+        if ($sanitizedAppId !== $appId) {
252
+            return false;
253
+        }
254
+        static $app_dir = [];
255
+
256
+        if (isset($app_dir[$appId]) && !$ignoreCache) {
257
+            return $app_dir[$appId];
258
+        }
259
+
260
+        $possibleApps = [];
261
+        foreach (OC::$APPSROOTS as $dir) {
262
+            if (file_exists($dir['path'] . '/' . $appId)) {
263
+                $possibleApps[] = $dir;
264
+            }
265
+        }
266
+
267
+        if (empty($possibleApps)) {
268
+            return false;
269
+        } elseif (count($possibleApps) === 1) {
270
+            $dir = array_shift($possibleApps);
271
+            $app_dir[$appId] = $dir;
272
+            return $dir;
273
+        } else {
274
+            $versionToLoad = [];
275
+            foreach ($possibleApps as $possibleApp) {
276
+                $version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
277
+                if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
278
+                    $versionToLoad = [
279
+                        'dir' => $possibleApp,
280
+                        'version' => $version,
281
+                    ];
282
+                }
283
+            }
284
+            $app_dir[$appId] = $versionToLoad['dir'];
285
+            return $versionToLoad['dir'];
286
+            //TODO - write test
287
+        }
288
+    }
289
+
290
+    /**
291
+     * Get the directory for the given app.
292
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
293
+     *
294
+     * @psalm-taint-specialize
295
+     *
296
+     * @param string $appId
297
+     * @param bool $refreshAppPath should be set to true only during install/upgrade
298
+     * @return string|false
299
+     * @deprecated 11.0.0 use Server::get(IAppManager)->getAppPath()
300
+     */
301
+    public static function getAppPath(string $appId, bool $refreshAppPath = false) {
302
+        $appId = self::cleanAppId($appId);
303
+        if ($appId === '') {
304
+            return false;
305
+        } elseif ($appId === 'core') {
306
+            return __DIR__ . '/../../../core';
307
+        }
308
+
309
+        if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
310
+            return $dir['path'] . '/' . $appId;
311
+        }
312
+        return false;
313
+    }
314
+
315
+    /**
316
+     * Get the path for the given app on the access
317
+     * If the app is defined in multiple directories, the first one is taken. (false if not found)
318
+     *
319
+     * @param string $appId
320
+     * @return string|false
321
+     * @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
322
+     */
323
+    public static function getAppWebPath(string $appId) {
324
+        if (($dir = self::findAppInDirectories($appId)) != false) {
325
+            return OC::$WEBROOT . $dir['url'] . '/' . $appId;
326
+        }
327
+        return false;
328
+    }
329
+
330
+    /**
331
+     * get app's version based on it's path
332
+     *
333
+     * @param string $path
334
+     * @return string
335
+     */
336
+    public static function getAppVersionByPath(string $path): string {
337
+        $infoFile = $path . '/appinfo/info.xml';
338
+        $appData = Server::get(IAppManager::class)->getAppInfoByPath($infoFile);
339
+        return $appData['version'] ?? '';
340
+    }
341
+
342
+    /**
343
+     * get the id of loaded app
344
+     *
345
+     * @return string
346
+     */
347
+    public static function getCurrentApp(): string {
348
+        if (\OC::$CLI) {
349
+            return '';
350
+        }
351
+
352
+        $request = \OC::$server->getRequest();
353
+        $script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
354
+        $topFolder = substr($script, 0, strpos($script, '/') ?: 0);
355
+        if (empty($topFolder)) {
356
+            try {
357
+                $path_info = $request->getPathInfo();
358
+            } catch (Exception $e) {
359
+                // Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
360
+                \OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
361
+                return '';
362
+            }
363
+            if ($path_info) {
364
+                $topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
365
+            }
366
+        }
367
+        if ($topFolder == 'apps') {
368
+            $length = strlen($topFolder);
369
+            return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
370
+        } else {
371
+            return $topFolder;
372
+        }
373
+    }
374
+
375
+    /**
376
+     * @param array $entry
377
+     * @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
378
+     */
379
+    public static function registerLogIn(array $entry) {
380
+        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');
381
+        self::$altLogin[] = $entry;
382
+    }
383
+
384
+    /**
385
+     * @return array
386
+     */
387
+    public static function getAlternativeLogIns(): array {
388
+        /** @var Coordinator $bootstrapCoordinator */
389
+        $bootstrapCoordinator = Server::get(Coordinator::class);
390
+
391
+        foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
392
+            if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
393
+                Server::get(LoggerInterface::class)->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
394
+                    'option' => $registration->getService(),
395
+                    'interface' => IAlternativeLogin::class,
396
+                    'app' => $registration->getAppId(),
397
+                ]);
398
+                continue;
399
+            }
400
+
401
+            try {
402
+                /** @var IAlternativeLogin $provider */
403
+                $provider = Server::get($registration->getService());
404
+            } catch (ContainerExceptionInterface $e) {
405
+                Server::get(LoggerInterface::class)->error('Alternative login option {option} can not be initialized.',
406
+                    [
407
+                        'exception' => $e,
408
+                        'option' => $registration->getService(),
409
+                        'app' => $registration->getAppId(),
410
+                    ]);
411
+            }
412
+
413
+            try {
414
+                $provider->load();
415
+
416
+                self::$altLogin[] = [
417
+                    'name' => $provider->getLabel(),
418
+                    'href' => $provider->getLink(),
419
+                    'class' => $provider->getClass(),
420
+                ];
421
+            } catch (Throwable $e) {
422
+                Server::get(LoggerInterface::class)->error('Alternative login option {option} had an error while loading.',
423
+                    [
424
+                        'exception' => $e,
425
+                        'option' => $registration->getService(),
426
+                        'app' => $registration->getAppId(),
427
+                    ]);
428
+            }
429
+        }
430
+
431
+        return self::$altLogin;
432
+    }
433
+
434
+    /**
435
+     * get a list of all apps in the apps folder
436
+     *
437
+     * @return string[] an array of app names (string IDs)
438
+     * @deprecated 31.0.0 Use IAppManager::getAllAppsInAppsFolders instead
439
+     */
440
+    public static function getAllApps(): array {
441
+        return Server::get(IAppManager::class)->getAllAppsInAppsFolders();
442
+    }
443
+
444
+    /**
445
+     * List all supported apps
446
+     *
447
+     * @deprecated 32.0.0 Use \OCP\Support\Subscription\IRegistry::delegateGetSupportedApps instead
448
+     */
449
+    public function getSupportedApps(): array {
450
+        $subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
451
+        $supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
452
+        return $supportedApps;
453
+    }
454
+
455
+    /**
456
+     * List all apps, this is used in apps.php
457
+     *
458
+     * @return array
459
+     */
460
+    public function listAllApps(): array {
461
+        $appManager = \OC::$server->getAppManager();
462
+
463
+        $installedApps = $appManager->getAllAppsInAppsFolders();
464
+        //we don't want to show configuration for these
465
+        $blacklist = $appManager->getAlwaysEnabledApps();
466
+        $appList = [];
467
+        $langCode = \OC::$server->getL10N('core')->getLanguageCode();
468
+        $urlGenerator = \OC::$server->getURLGenerator();
469
+        $supportedApps = $this->getSupportedApps();
470
+
471
+        foreach ($installedApps as $app) {
472
+            if (!in_array($app, $blacklist)) {
473
+                $info = $appManager->getAppInfo($app, false, $langCode);
474
+                if (!is_array($info)) {
475
+                    Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
476
+                    continue;
477
+                }
478
+
479
+                if (!isset($info['name'])) {
480
+                    Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
481
+                    continue;
482
+                }
483
+
484
+                $enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
485
+                $info['groups'] = null;
486
+                if ($enabled === 'yes') {
487
+                    $active = true;
488
+                } elseif ($enabled === 'no') {
489
+                    $active = false;
490
+                } else {
491
+                    $active = true;
492
+                    $info['groups'] = $enabled;
493
+                }
494
+
495
+                $info['active'] = $active;
496
+
497
+                if ($appManager->isShipped($app)) {
498
+                    $info['internal'] = true;
499
+                    $info['level'] = self::officialApp;
500
+                    $info['removable'] = false;
501
+                } else {
502
+                    $info['internal'] = false;
503
+                    $info['removable'] = true;
504
+                }
505
+
506
+                if (in_array($app, $supportedApps)) {
507
+                    $info['level'] = self::supportedApp;
508
+                }
509
+
510
+                $appPath = self::getAppPath($app);
511
+                if ($appPath !== false) {
512
+                    $appIcon = $appPath . '/img/' . $app . '.svg';
513
+                    if (file_exists($appIcon)) {
514
+                        $info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
515
+                        $info['previewAsIcon'] = true;
516
+                    } else {
517
+                        $appIcon = $appPath . '/img/app.svg';
518
+                        if (file_exists($appIcon)) {
519
+                            $info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
520
+                            $info['previewAsIcon'] = true;
521
+                        }
522
+                    }
523
+                }
524
+                // fix documentation
525
+                if (isset($info['documentation']) && is_array($info['documentation'])) {
526
+                    foreach ($info['documentation'] as $key => $url) {
527
+                        // If it is not an absolute URL we assume it is a key
528
+                        // i.e. admin-ldap will get converted to go.php?to=admin-ldap
529
+                        if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
530
+                            $url = $urlGenerator->linkToDocs($url);
531
+                        }
532
+
533
+                        $info['documentation'][$key] = $url;
534
+                    }
535
+                }
536
+
537
+                $info['version'] = $appManager->getAppVersion($app);
538
+                $appList[] = $info;
539
+            }
540
+        }
541
+
542
+        return $appList;
543
+    }
544
+
545
+    public static function shouldUpgrade(string $app): bool {
546
+        $versions = self::getAppVersions();
547
+        $currentVersion = Server::get(\OCP\App\IAppManager::class)->getAppVersion($app);
548
+        if ($currentVersion && isset($versions[$app])) {
549
+            $installedVersion = $versions[$app];
550
+            if (!version_compare($currentVersion, $installedVersion, '=')) {
551
+                return true;
552
+            }
553
+        }
554
+        return false;
555
+    }
556
+
557
+    /**
558
+     * Adjust the number of version parts of $version1 to match
559
+     * the number of version parts of $version2.
560
+     *
561
+     * @param string $version1 version to adjust
562
+     * @param string $version2 version to take the number of parts from
563
+     * @return string shortened $version1
564
+     */
565
+    private static function adjustVersionParts(string $version1, string $version2): string {
566
+        $version1 = explode('.', $version1);
567
+        $version2 = explode('.', $version2);
568
+        // reduce $version1 to match the number of parts in $version2
569
+        while (count($version1) > count($version2)) {
570
+            array_pop($version1);
571
+        }
572
+        // if $version1 does not have enough parts, add some
573
+        while (count($version1) < count($version2)) {
574
+            $version1[] = '0';
575
+        }
576
+        return implode('.', $version1);
577
+    }
578
+
579
+    /**
580
+     * Check whether the current Nextcloud version matches the given
581
+     * application's version requirements.
582
+     *
583
+     * The comparison is made based on the number of parts that the
584
+     * app info version has. For example for ownCloud 6.0.3 if the
585
+     * app info version is expecting version 6.0, the comparison is
586
+     * made on the first two parts of the ownCloud version.
587
+     * This means that it's possible to specify "requiremin" => 6
588
+     * and "requiremax" => 6 and it will still match ownCloud 6.0.3.
589
+     *
590
+     * @param string $ocVersion Nextcloud version to check against
591
+     * @param array $appInfo app info (from xml)
592
+     *
593
+     * @return boolean true if compatible, otherwise false
594
+     */
595
+    public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
596
+        $requireMin = '';
597
+        $requireMax = '';
598
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
599
+            $requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
600
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
601
+            $requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
602
+        } elseif (isset($appInfo['requiremin'])) {
603
+            $requireMin = $appInfo['requiremin'];
604
+        } elseif (isset($appInfo['require'])) {
605
+            $requireMin = $appInfo['require'];
606
+        }
607
+
608
+        if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
609
+            $requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
610
+        } elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
611
+            $requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
612
+        } elseif (isset($appInfo['requiremax'])) {
613
+            $requireMax = $appInfo['requiremax'];
614
+        }
615
+
616
+        if (!empty($requireMin)
617
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
618
+        ) {
619
+            return false;
620
+        }
621
+
622
+        if (!$ignoreMax && !empty($requireMax)
623
+            && version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
624
+        ) {
625
+            return false;
626
+        }
627
+
628
+        return true;
629
+    }
630
+
631
+    /**
632
+     * get the installed version of all apps
633
+     * @deprecated 32.0.0 Use IAppManager::getAppInstalledVersions or IAppConfig::getAppInstalledVersions instead
634
+     */
635
+    public static function getAppVersions(): array {
636
+        return Server::get(IAppConfig::class)->getAppInstalledVersions();
637
+    }
638
+
639
+    /**
640
+     * update the database for the app and call the update script
641
+     *
642
+     * @param string $appId
643
+     * @return bool
644
+     */
645
+    public static function updateApp(string $appId): bool {
646
+        // for apps distributed with core, we refresh app path in case the downloaded version
647
+        // have been installed in custom apps and not in the default path
648
+        $appPath = self::getAppPath($appId, true);
649
+        if ($appPath === false) {
650
+            return false;
651
+        }
652
+
653
+        if (is_file($appPath . '/appinfo/database.xml')) {
654
+            Server::get(LoggerInterface::class)->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
655
+            return false;
656
+        }
657
+
658
+        \OC::$server->getAppManager()->clearAppsCache();
659
+        $l = \OC::$server->getL10N('core');
660
+        $appData = Server::get(\OCP\App\IAppManager::class)->getAppInfo($appId, false, $l->getLanguageCode());
661
+
662
+        $ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
663
+        $ignoreMax = in_array($appId, $ignoreMaxApps, true);
664
+        \OC_App::checkAppDependencies(
665
+            \OC::$server->getConfig(),
666
+            $l,
667
+            $appData,
668
+            $ignoreMax
669
+        );
670
+
671
+        self::registerAutoloading($appId, $appPath, true);
672
+        self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
673
+
674
+        $ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
675
+        $ms->migrate();
676
+
677
+        self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
678
+        self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
679
+        // update appversion in app manager
680
+        \OC::$server->getAppManager()->clearAppsCache();
681
+        \OC::$server->getAppManager()->getAppVersion($appId, false);
682
+
683
+        self::setupBackgroundJobs($appData['background-jobs']);
684
+
685
+        //set remote/public handlers
686
+        if (array_key_exists('ocsid', $appData)) {
687
+            \OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
688
+        } elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
689
+            \OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
690
+        }
691
+        foreach ($appData['remote'] as $name => $path) {
692
+            \OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
693
+        }
694
+        foreach ($appData['public'] as $name => $path) {
695
+            \OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
696
+        }
697
+
698
+        self::setAppTypes($appId);
699
+
700
+        $version = Server::get(\OCP\App\IAppManager::class)->getAppVersion($appId);
701
+        \OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
702
+
703
+        // migrate eventual new config keys in the process
704
+        /** @psalm-suppress InternalMethod */
705
+        Server::get(ConfigManager::class)->migrateConfigLexiconKeys($appId);
706
+
707
+        \OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
708
+        \OC::$server->get(IEventDispatcher::class)->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
709
+            ManagerEvent::EVENT_APP_UPDATE, $appId
710
+        ));
711
+
712
+        return true;
713
+    }
714
+
715
+    /**
716
+     * @param string $appId
717
+     * @param string[] $steps
718
+     * @throws \OC\NeedsUpdateException
719
+     */
720
+    public static function executeRepairSteps(string $appId, array $steps) {
721
+        if (empty($steps)) {
722
+            return;
723
+        }
724
+        // load the app
725
+        self::loadApp($appId);
726
+
727
+        $dispatcher = Server::get(IEventDispatcher::class);
728
+
729
+        // load the steps
730
+        $r = Server::get(Repair::class);
731
+        foreach ($steps as $step) {
732
+            try {
733
+                $r->addStep($step);
734
+            } catch (Exception $ex) {
735
+                $dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
736
+                logger('core')->error('Failed to add app migration step ' . $step, ['exception' => $ex]);
737
+            }
738
+        }
739
+        // run the steps
740
+        $r->run();
741
+    }
742
+
743
+    public static function setupBackgroundJobs(array $jobs) {
744
+        $queue = \OC::$server->getJobList();
745
+        foreach ($jobs as $job) {
746
+            $queue->add($job);
747
+        }
748
+    }
749
+
750
+    /**
751
+     * @param string $appId
752
+     * @param string[] $steps
753
+     */
754
+    private static function setupLiveMigrations(string $appId, array $steps) {
755
+        $queue = \OC::$server->getJobList();
756
+        foreach ($steps as $step) {
757
+            $queue->add('OC\Migration\BackgroundRepair', [
758
+                'app' => $appId,
759
+                'step' => $step]);
760
+        }
761
+    }
762
+
763
+    /**
764
+     * @param \OCP\IConfig $config
765
+     * @param \OCP\IL10N $l
766
+     * @param array $info
767
+     * @throws \Exception
768
+     */
769
+    public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
770
+        $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
771
+        $missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
772
+        if (!empty($missing)) {
773
+            $missingMsg = implode(PHP_EOL, $missing);
774
+            throw new \Exception(
775
+                $l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
776
+                    [$info['name'], $missingMsg]
777
+                )
778
+            );
779
+        }
780
+    }
781 781
 }
Please login to merge, or discard this patch.