Passed
Push — master ( 8996b9...d40b21 )
by Morris
10:42 queued 10s
created

isCorrectMemcachedPHPModuleInstalled()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
nc 3
nop 0
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Derek <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Ko- <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin McCorkell <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OC\Settings\Controller;
32
33
use bantu\IniGetWrapper\IniGetWrapper;
34
use DirectoryIterator;
35
use Doctrine\DBAL\DBALException;
36
use Doctrine\DBAL\Platforms\SqlitePlatform;
37
use Doctrine\DBAL\Types\Type;
38
use GuzzleHttp\Exception\ClientException;
39
use OC;
40
use OC\AppFramework\Http;
41
use OC\DB\Connection;
42
use OC\DB\MissingIndexInformation;
43
use OC\DB\SchemaWrapper;
44
use OC\IntegrityCheck\Checker;
45
use OC\Lock\NoopLockingProvider;
46
use OC\MemoryInfo;
47
use OCP\AppFramework\Controller;
48
use OCP\AppFramework\Http\DataDisplayResponse;
49
use OCP\AppFramework\Http\DataResponse;
50
use OCP\AppFramework\Http\RedirectResponse;
51
use OCP\Http\Client\IClientService;
52
use OCP\IConfig;
53
use OCP\IDateTimeFormatter;
54
use OCP\IDBConnection;
55
use OCP\IL10N;
56
use OCP\ILogger;
57
use OCP\IRequest;
58
use OCP\IURLGenerator;
59
use OCP\Lock\ILockingProvider;
60
use OCP\Security\ISecureRandom;
61
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
62
use Symfony\Component\EventDispatcher\GenericEvent;
63
64
/**
65
 * @package OC\Settings\Controller
66
 */
67
class CheckSetupController extends Controller {
68
	/** @var IConfig */
69
	private $config;
70
	/** @var IClientService */
71
	private $clientService;
72
	/** @var \OC_Util */
73
	private $util;
74
	/** @var IURLGenerator */
75
	private $urlGenerator;
76
	/** @var IL10N */
77
	private $l10n;
78
	/** @var Checker */
79
	private $checker;
80
	/** @var ILogger */
81
	private $logger;
82
	/** @var EventDispatcherInterface */
83
	private $dispatcher;
84
	/** @var IDBConnection|Connection */
85
	private $db;
86
	/** @var ILockingProvider */
87
	private $lockingProvider;
88
	/** @var IDateTimeFormatter */
89
	private $dateTimeFormatter;
90
	/** @var MemoryInfo */
91
	private $memoryInfo;
92
	/** @var ISecureRandom */
93
	private $secureRandom;
94
95
	public function __construct($AppName,
96
								IRequest $request,
97
								IConfig $config,
98
								IClientService $clientService,
99
								IURLGenerator $urlGenerator,
100
								\OC_Util $util,
101
								IL10N $l10n,
102
								Checker $checker,
103
								ILogger $logger,
104
								EventDispatcherInterface $dispatcher,
105
								IDBConnection $db,
106
								ILockingProvider $lockingProvider,
107
								IDateTimeFormatter $dateTimeFormatter,
108
								MemoryInfo $memoryInfo,
109
								ISecureRandom $secureRandom) {
110
		parent::__construct($AppName, $request);
111
		$this->config = $config;
112
		$this->clientService = $clientService;
113
		$this->util = $util;
114
		$this->urlGenerator = $urlGenerator;
115
		$this->l10n = $l10n;
116
		$this->checker = $checker;
117
		$this->logger = $logger;
118
		$this->dispatcher = $dispatcher;
119
		$this->db = $db;
120
		$this->lockingProvider = $lockingProvider;
121
		$this->dateTimeFormatter = $dateTimeFormatter;
122
		$this->memoryInfo = $memoryInfo;
123
		$this->secureRandom = $secureRandom;
124
	}
125
126
	/**
127
	 * Checks if the server can connect to the internet using HTTPS and HTTP
128
	 * @return bool
129
	 */
130
	private function isInternetConnectionWorking() {
131
		if ($this->config->getSystemValue('has_internet_connection', true) === false) {
132
			return false;
133
		}
134
135
		$siteArray = $this->config->getSystemValue('connectivity_check_domains', [
136
			'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
137
		]);
138
139
		foreach($siteArray as $site) {
140
			if ($this->isSiteReachable($site)) {
141
				return true;
142
			}
143
		}
144
		return false;
145
	}
146
147
	/**
148
	* Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
149
	* @return bool
150
	*/
151
	private function isSiteReachable($sitename) {
152
		$httpSiteName = 'http://' . $sitename . '/';
153
		$httpsSiteName = 'https://' . $sitename . '/';
154
155
		try {
156
			$client = $this->clientService->newClient();
157
			$client->get($httpSiteName);
158
			$client->get($httpsSiteName);
159
		} catch (\Exception $e) {
160
			$this->logger->logException($e, ['app' => 'internet_connection_check']);
161
			return false;
162
		}
163
		return true;
164
	}
165
166
	/**
167
	 * Checks whether a local memcache is installed or not
168
	 * @return bool
169
	 */
170
	private function isMemcacheConfigured() {
171
		return $this->config->getSystemValue('memcache.local', null) !== null;
172
	}
173
174
	/**
175
	 * Whether PHP can generate "secure" pseudorandom integers
176
	 *
177
	 * @return bool
178
	 */
179
	private function isRandomnessSecure() {
180
		try {
181
			$this->secureRandom->generate(1);
182
		} catch (\Exception $ex) {
183
			return false;
184
		}
185
		return true;
186
	}
187
188
	/**
189
	 * Public for the sake of unit-testing
190
	 *
191
	 * @return array
192
	 */
193
	protected function getCurlVersion() {
194
		return curl_version();
195
	}
196
197
	/**
198
	 * Check if the used  SSL lib is outdated. Older OpenSSL and NSS versions do
199
	 * have multiple bugs which likely lead to problems in combination with
200
	 * functionality required by ownCloud such as SNI.
201
	 *
202
	 * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
203
	 * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
204
	 * @return string
205
	 */
206
	private function isUsedTlsLibOutdated() {
207
		// Don't run check when:
208
		// 1. Server has `has_internet_connection` set to false
209
		// 2. AppStore AND S2S is disabled
210
		if(!$this->config->getSystemValue('has_internet_connection', true)) {
211
			return '';
212
		}
213
		if(!$this->config->getSystemValue('appstoreenabled', true)
214
			&& $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
215
			&& $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
216
			return '';
217
		}
218
219
		$versionString = $this->getCurlVersion();
220
		if(isset($versionString['ssl_version'])) {
221
			$versionString = $versionString['ssl_version'];
222
		} else {
223
			return '';
224
		}
225
226
		$features = (string)$this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
227
		if(!$this->config->getSystemValue('appstoreenabled', true)) {
228
			$features = (string)$this->l10n->t('Federated Cloud Sharing');
229
		}
230
231
		// Check if at least OpenSSL after 1.01d or 1.0.2b
232
		if(strpos($versionString, 'OpenSSL/') === 0) {
233
			$majorVersion = substr($versionString, 8, 5);
234
			$patchRelease = substr($versionString, 13, 6);
235
236
			if(($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
237
				($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
238
				return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['OpenSSL', $versionString, $features]);
239
			}
240
		}
241
242
		// Check if NSS and perform heuristic check
243
		if(strpos($versionString, 'NSS/') === 0) {
244
			try {
245
				$firstClient = $this->clientService->newClient();
246
				$firstClient->get('https://nextcloud.com/');
247
248
				$secondClient = $this->clientService->newClient();
249
				$secondClient->get('https://nextcloud.com/');
250
			} catch (ClientException $e) {
251
				if($e->getResponse()->getStatusCode() === 400) {
252
					return $this->l10n->t('cURL is using an outdated %1$s version (%2$s). Please update your operating system or features such as %3$s will not work reliably.', ['NSS', $versionString, $features]);
253
				}
254
			}
255
		}
256
257
		return '';
258
	}
259
260
	/**
261
	 * Whether the version is outdated
262
	 *
263
	 * @return bool
264
	 */
265
	protected function isPhpOutdated() {
266
		if (version_compare(PHP_VERSION, '7.1.0', '<')) {
267
			return true;
268
		}
269
270
		return false;
271
	}
272
273
	/**
274
	 * Whether the php version is still supported (at time of release)
275
	 * according to: https://secure.php.net/supported-versions.php
276
	 *
277
	 * @return array
278
	 */
279
	private function isPhpSupported() {
280
		return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
281
	}
282
283
	/**
284
	 * Check if the reverse proxy configuration is working as expected
285
	 *
286
	 * @return bool
287
	 */
288
	private function forwardedForHeadersWorking() {
289
		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
290
		$remoteAddress = $this->request->getHeader('REMOTE_ADDR');
291
292
		if (empty($trustedProxies) && $this->request->getHeader('X-Forwarded-Host')) {
293
			return false;
294
		}
295
296
		if (\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies, true)) {
297
			return $remoteAddress !== $this->request->getRemoteAddress();
298
		}
299
300
		// either not enabled or working correctly
301
		return true;
302
	}
303
304
	/**
305
	 * Checks if the correct memcache module for PHP is installed. Only
306
	 * fails if memcached is configured and the working module is not installed.
307
	 *
308
	 * @return bool
309
	 */
310
	private function isCorrectMemcachedPHPModuleInstalled() {
311
		if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
312
			return true;
313
		}
314
315
		// there are two different memcached modules for PHP
316
		// we only support memcached and not memcache
317
		// https://code.google.com/p/memcached/wiki/PHPClientComparison
318
		return !(!extension_loaded('memcached') && extension_loaded('memcache'));
319
	}
320
321
	/**
322
	 * Checks if set_time_limit is not disabled.
323
	 *
324
	 * @return bool
325
	 */
326
	private function isSettimelimitAvailable() {
327
		if (function_exists('set_time_limit')
328
			&& strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
329
			return true;
330
		}
331
332
		return false;
333
	}
334
335
	/**
336
	 * @return RedirectResponse
337
	 */
338
	public function rescanFailedIntegrityCheck() {
339
		$this->checker->runInstanceVerification();
340
		return new RedirectResponse(
341
			$this->urlGenerator->linkToRoute('settings.AdminSettings.index')
342
		);
343
	}
344
345
	/**
346
	 * @NoCSRFRequired
347
	 * @return DataResponse
348
	 */
349
	public function getFailedIntegrityCheckFiles() {
350
		if(!$this->checker->isCodeCheckEnforced()) {
351
			return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
0 ignored issues
show
Bug Best Practice introduced by
The expression return new OCP\AppFramew...y cannot be verified.') returns the type OCP\AppFramework\Http\DataDisplayResponse which is incompatible with the documented return type OCP\AppFramework\Http\DataResponse.
Loading history...
352
		}
353
354
		$completeResults = $this->checker->getResults();
355
356
		if(!empty($completeResults)) {
357
			$formattedTextResponse = 'Technical information
358
=====================
359
The following list covers which files have failed the integrity check. Please read
360
the previous linked documentation to learn more about the errors and how to fix
361
them.
362
363
Results
364
=======
365
';
366
			foreach($completeResults as $context => $contextResult) {
367
				$formattedTextResponse .= "- $context\n";
368
369
				foreach($contextResult as $category => $result) {
370
					$formattedTextResponse .= "\t- $category\n";
371
					if($category !== 'EXCEPTION') {
372
						foreach ($result as $key => $results) {
373
							$formattedTextResponse .= "\t\t- $key\n";
374
						}
375
					} else {
376
						foreach ($result as $key => $results) {
377
							$formattedTextResponse .= "\t\t- $results\n";
378
						}
379
					}
380
381
				}
382
			}
383
384
			$formattedTextResponse .= '
385
Raw output
386
==========
387
';
388
			$formattedTextResponse .= print_r($completeResults, true);
389
		} else {
390
			$formattedTextResponse = 'No errors have been found.';
391
		}
392
393
394
		$response = new DataDisplayResponse(
395
			$formattedTextResponse,
396
			Http::STATUS_OK,
397
			[
398
				'Content-Type' => 'text/plain',
399
			]
400
		);
401
402
		return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type OCP\AppFramework\Http\DataDisplayResponse which is incompatible with the documented return type OCP\AppFramework\Http\DataResponse.
Loading history...
403
	}
404
405
	/**
406
	 * Checks whether a PHP opcache is properly set up
407
	 * @return bool
408
	 */
409
	protected function isOpcacheProperlySetup() {
410
		$iniWrapper = new IniGetWrapper();
411
412
		if(!$iniWrapper->getBool('opcache.enable')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $iniWrapper->getBool('opcache.enable') of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
413
			return false;
414
		}
415
416
		if(!$iniWrapper->getBool('opcache.save_comments')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $iniWrapper->getBool('opcache.save_comments') of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
417
			return false;
418
		}
419
420
		if(!$iniWrapper->getBool('opcache.enable_cli')) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $iniWrapper->getBool('opcache.enable_cli') of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
421
			return false;
422
		}
423
424
		if($iniWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
425
			return false;
426
		}
427
428
		if($iniWrapper->getNumeric('opcache.memory_consumption') < 128) {
429
			return false;
430
		}
431
432
		if($iniWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
433
			return false;
434
		}
435
436
		return true;
437
	}
438
439
	/**
440
	 * Check if the required FreeType functions are present
441
	 * @return bool
442
	 */
443
	protected function hasFreeTypeSupport() {
444
		return function_exists('imagettfbbox') && function_exists('imagettftext');
445
	}
446
447
	protected function hasMissingIndexes(): array {
448
		$indexInfo = new MissingIndexInformation();
449
		// Dispatch event so apps can also hint for pending index updates if needed
450
		$event = new GenericEvent($indexInfo);
451
		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
452
453
		return $indexInfo->getListOfMissingIndexes();
454
	}
455
456
	protected function isSqliteUsed() {
457
		return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
458
	}
459
460
	protected function isReadOnlyConfig(): bool {
461
		return \OC_Helper::isReadOnlyConfigEnabled();
462
	}
463
464
	protected function hasValidTransactionIsolationLevel(): bool {
465
		try {
466
			if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
467
				return true;
468
			}
469
470
			return $this->db->getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
0 ignored issues
show
Bug introduced by
The method getTransactionIsolation() does not exist on OCP\IDBConnection. Since it exists in all sub-types, consider adding an abstract or default implementation to OCP\IDBConnection. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

470
			return $this->db->/** @scrutinizer ignore-call */ getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
Loading history...
Deprecated Code introduced by
The constant Doctrine\DBAL\Connection...NSACTION_READ_COMMITTED has been deprecated: Use TransactionIsolationLevel::READ_COMMITTED. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

470
			return $this->db->getTransactionIsolation() === /** @scrutinizer ignore-deprecated */ Connection::TRANSACTION_READ_COMMITTED;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
471
		} catch (DBALException $e) {
472
			// ignore
473
		}
474
475
		return true;
476
	}
477
478
	protected function hasFileinfoInstalled(): bool {
479
		return \OC_Util::fileInfoLoaded();
480
	}
481
482
	protected function hasWorkingFileLocking(): bool {
483
		return !($this->lockingProvider instanceof NoopLockingProvider);
484
	}
485
486
	protected function getSuggestedOverwriteCliURL(): string {
487
		$suggestedOverwriteCliUrl = '';
488
		if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
489
			$suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
490
			if (!$this->config->getSystemValue('config_is_read_only', false)) {
491
				// Set the overwrite URL when it was not set yet.
492
				$this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
493
				$suggestedOverwriteCliUrl = '';
494
			}
495
		}
496
		return $suggestedOverwriteCliUrl;
497
	}
498
499
	protected function getLastCronInfo(): array {
500
		$lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
501
		return [
502
			'diffInSeconds' => time() - $lastCronRun,
503
			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
0 ignored issues
show
Bug introduced by
$lastCronRun of type string is incompatible with the type DateTime|integer expected by parameter $timestamp of OCP\IDateTimeFormatter::formatTimeSpan(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

503
			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan(/** @scrutinizer ignore-type */ $lastCronRun),
Loading history...
504
			'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
505
		];
506
	}
507
508
	protected function getCronErrors() {
509
		$errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
510
511
		if (is_array($errors)) {
512
			return $errors;
513
		}
514
515
		return [];
516
	}
517
518
	protected function isPHPMailerUsed(): bool {
519
		return $this->config->getSystemValue('mail_smtpmode', 'smtp') === 'php';
520
	}
521
522
	protected function hasOpcacheLoaded(): bool {
523
		return function_exists('opcache_get_status');
524
	}
525
526
	/**
527
	 * Iterates through the configured app roots and
528
	 * tests if the subdirectories are owned by the same user than the current user.
529
	 *
530
	 * @return array
531
	 */
532
	protected function getAppDirsWithDifferentOwner(): array {
533
		$currentUser = posix_getuid();
534
		$appDirsWithDifferentOwner = [[]];
535
536
		foreach (OC::$APPSROOTS as $appRoot) {
537
			if ($appRoot['writable'] === true) {
538
				$appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
539
			}
540
		}
541
542
		$appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
543
		sort($appDirsWithDifferentOwner);
544
545
		return $appDirsWithDifferentOwner;
546
	}
547
548
	/**
549
	 * Tests if the directories for one apps directory are writable by the current user.
550
	 *
551
	 * @param int $currentUser The current user
552
	 * @param array $appRoot The app root config
553
	 * @return string[] The none writable directory paths inside the app root
554
	 */
555
	private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
556
		$appDirsWithDifferentOwner = [];
557
		$appsPath = $appRoot['path'];
558
		$appsDir = new DirectoryIterator($appRoot['path']);
559
560
		foreach ($appsDir as $fileInfo) {
561
			if ($fileInfo->isDir() && !$fileInfo->isDot()) {
562
				$absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
563
				$appDirUser = fileowner($absAppPath);
564
				if ($appDirUser !== $currentUser) {
565
					$appDirsWithDifferentOwner[] = $absAppPath;
566
				}
567
			}
568
		}
569
570
		return $appDirsWithDifferentOwner;
571
	}
572
573
	/**
574
	 * Checks for potential PHP modules that would improve the instance
575
	 *
576
	 * @return string[] A list of PHP modules that is recommended
577
	 */
578
	protected function hasRecommendedPHPModules(): array {
579
		$recommendedPHPModules = [];
580
581
		if (!function_exists('grapheme_strlen')) {
582
			$recommendedPHPModules[] = 'intl';
583
		}
584
585
		if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
586
			if (!extension_loaded('imagick')) {
587
				$recommendedPHPModules[] = 'imagick';
588
			}
589
		}
590
591
		return $recommendedPHPModules;
592
	}
593
594
	protected function isMysqlUsedWithoutUTF8MB4(): bool {
595
		return ($this->config->getSystemValue('dbtype', 'sqlite') === 'mysql') && ($this->config->getSystemValue('mysql.utf8mb4', false) === false);
596
	}
597
598
	protected function hasBigIntConversionPendingColumns(): array {
599
		// copy of ConvertFilecacheBigInt::getColumnsByTable()
600
		$tables = [
601
			'activity' => ['activity_id', 'object_id'],
602
			'activity_mq' => ['mail_id'],
603
			'filecache' => ['fileid', 'storage', 'parent', 'mimetype', 'mimepart', 'mtime', 'storage_mtime'],
604
			'mimetypes' => ['id'],
605
			'storages' => ['numeric_id'],
606
		];
607
608
		$schema = new SchemaWrapper($this->db);
609
		$isSqlite = $this->db->getDatabasePlatform() instanceof SqlitePlatform;
610
		$pendingColumns = [];
611
612
		foreach ($tables as $tableName => $columns) {
613
			if (!$schema->hasTable($tableName)) {
614
				continue;
615
			}
616
617
			$table = $schema->getTable($tableName);
618
			foreach ($columns as $columnName) {
619
				$column = $table->getColumn($columnName);
620
				$isAutoIncrement = $column->getAutoincrement();
621
				$isAutoIncrementOnSqlite = $isSqlite && $isAutoIncrement;
622
				if ($column->getType()->getName() !== Type::BIGINT && !$isAutoIncrementOnSqlite) {
623
					$pendingColumns[] = $tableName . '.' . $columnName;
624
				}
625
			}
626
		}
627
628
		return $pendingColumns;
629
	}
630
631
	protected function isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(): bool {
632
		$objectStore = $this->config->getSystemValue('objectstore', null);
633
		$objectStoreMultibucket = $this->config->getSystemValue('objectstore_multibucket', null);
634
635
		if (!isset($objectStoreMultibucket) && !isset($objectStore)) {
636
			return true;
637
		}
638
639
		if (isset($objectStoreMultibucket['class']) && $objectStoreMultibucket['class'] !== 'OC\\Files\\ObjectStore\\S3') {
640
			return true;
641
		}
642
643
		if (isset($objectStore['class']) && $objectStore['class'] !== 'OC\\Files\\ObjectStore\\S3') {
644
			return true;
645
		}
646
647
		$tempPath = sys_get_temp_dir();
648
		if (!is_dir($tempPath)) {
649
			$this->logger->error('Error while checking the temporary PHP path - it was not properly set to a directory. value: ' . $tempPath);
650
			return false;
651
		}
652
		$freeSpaceInTemp = disk_free_space($tempPath);
653
		if ($freeSpaceInTemp === false) {
654
			$this->logger->error('Error while checking the available disk space of temporary PHP path - no free disk space returned. temporary path: ' . $tempPath);
655
			return false;
656
		}
657
658
		$freeSpaceInTempInGB = $freeSpaceInTemp / 1024 / 1024 / 1024;
659
		if ($freeSpaceInTempInGB > 50) {
660
			return true;
661
		}
662
663
		$this->logger->warning('Checking the available space in the temporary path resulted in ' . round($freeSpaceInTempInGB, 1) . ' GB instead of the recommended 50GB. Path: ' . $tempPath);
664
		return false;
665
	}
666
667
	/**
668
	 * @return DataResponse
669
	 */
670
	public function check() {
671
		return new DataResponse(
672
			[
673
				'isGetenvServerWorking' => !empty(getenv('PATH')),
674
				'isReadOnlyConfig' => $this->isReadOnlyConfig(),
675
				'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
676
				'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
677
				'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
678
				'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
679
				'cronInfo' => $this->getLastCronInfo(),
680
				'cronErrors' => $this->getCronErrors(),
681
				'serverHasInternetConnection' => $this->isInternetConnectionWorking(),
682
				'isMemcacheConfigured' => $this->isMemcacheConfigured(),
683
				'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
684
				'isRandomnessSecure' => $this->isRandomnessSecure(),
685
				'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
686
				'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
687
				'phpSupported' => $this->isPhpSupported(),
688
				'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
689
				'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
690
				'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
691
				'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
692
				'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
693
				'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
694
				'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
695
				'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
696
				'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
697
				'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
698
				'missingIndexes' => $this->hasMissingIndexes(),
699
				'isSqliteUsed' => $this->isSqliteUsed(),
700
				'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
701
				'isPHPMailerUsed' => $this->isPHPMailerUsed(),
702
				'mailSettingsDocumentation' => $this->urlGenerator->getAbsoluteURL('index.php/settings/admin'),
703
				'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
704
				'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
705
				'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
706
				'pendingBigIntConversionColumns' => $this->hasBigIntConversionPendingColumns(),
707
				'isMysqlUsedWithoutUTF8MB4' => $this->isMysqlUsedWithoutUTF8MB4(),
708
				'isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed' => $this->isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed(),
709
			]
710
		);
711
	}
712
}
713