Passed
Push — master ( 922fd4...faa988 )
by Morris
10:17
created

CheckSetupController::getCurlVersion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
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 GuzzleHttp\Exception\ClientException;
38
use OC;
39
use OC\AppFramework\Http;
40
use OC\DB\Connection;
41
use OC\DB\MissingIndexInformation;
42
use OC\IntegrityCheck\Checker;
43
use OC\Lock\NoopLockingProvider;
44
use OC\MemoryInfo;
45
use OCP\AppFramework\Controller;
46
use OCP\AppFramework\Http\DataDisplayResponse;
47
use OCP\AppFramework\Http\DataResponse;
48
use OCP\AppFramework\Http\RedirectResponse;
49
use OCP\Http\Client\IClientService;
50
use OCP\IConfig;
51
use OCP\IDateTimeFormatter;
52
use OCP\IDBConnection;
53
use OCP\IL10N;
54
use OCP\ILogger;
55
use OCP\IRequest;
56
use OCP\IURLGenerator;
57
use OCP\Lock\ILockingProvider;
58
use OCP\Security\ISecureRandom;
59
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
60
use Symfony\Component\EventDispatcher\GenericEvent;
61
62
/**
63
 * @package OC\Settings\Controller
64
 */
65
class CheckSetupController extends Controller {
66
	/** @var IConfig */
67
	private $config;
68
	/** @var IClientService */
69
	private $clientService;
70
	/** @var \OC_Util */
71
	private $util;
72
	/** @var IURLGenerator */
73
	private $urlGenerator;
74
	/** @var IL10N */
75
	private $l10n;
76
	/** @var Checker */
77
	private $checker;
78
	/** @var ILogger */
79
	private $logger;
80
	/** @var EventDispatcherInterface */
81
	private $dispatcher;
82
	/** @var IDBConnection|Connection */
83
	private $db;
84
	/** @var ILockingProvider */
85
	private $lockingProvider;
86
	/** @var IDateTimeFormatter */
87
	private $dateTimeFormatter;
88
	/** @var MemoryInfo */
89
	private $memoryInfo;
90
	/** @var ISecureRandom */
91
	private $secureRandom;
92
93
	public function __construct($AppName,
94
								IRequest $request,
95
								IConfig $config,
96
								IClientService $clientService,
97
								IURLGenerator $urlGenerator,
98
								\OC_Util $util,
99
								IL10N $l10n,
100
								Checker $checker,
101
								ILogger $logger,
102
								EventDispatcherInterface $dispatcher,
103
								IDBConnection $db,
104
								ILockingProvider $lockingProvider,
105
								IDateTimeFormatter $dateTimeFormatter,
106
								MemoryInfo $memoryInfo,
107
								ISecureRandom $secureRandom) {
108
		parent::__construct($AppName, $request);
109
		$this->config = $config;
110
		$this->clientService = $clientService;
111
		$this->util = $util;
112
		$this->urlGenerator = $urlGenerator;
113
		$this->l10n = $l10n;
114
		$this->checker = $checker;
115
		$this->logger = $logger;
116
		$this->dispatcher = $dispatcher;
117
		$this->db = $db;
118
		$this->lockingProvider = $lockingProvider;
119
		$this->dateTimeFormatter = $dateTimeFormatter;
120
		$this->memoryInfo = $memoryInfo;
121
		$this->secureRandom = $secureRandom;
122
	}
123
124
	/**
125
	 * Checks if the server can connect to the internet using HTTPS and HTTP
126
	 * @return bool
127
	 */
128
	private function isInternetConnectionWorking() {
129
		if ($this->config->getSystemValue('has_internet_connection', true) === false) {
130
			return false;
131
		}
132
133
		$siteArray = $this->config->getSystemValue('connectivity_check_domains', [
134
			'www.nextcloud.com', 'www.startpage.com', 'www.eff.org', 'www.edri.org'
135
		]);
136
137
		foreach($siteArray as $site) {
138
			if ($this->isSiteReachable($site)) {
139
				return true;
140
			}
141
		}
142
		return false;
143
	}
144
145
	/**
146
	* Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
147
	* @return bool
148
	*/
149
	private function isSiteReachable($sitename) {
150
		$httpSiteName = 'http://' . $sitename . '/';
151
		$httpsSiteName = 'https://' . $sitename . '/';
152
153
		try {
154
			$client = $this->clientService->newClient();
155
			$client->get($httpSiteName);
156
			$client->get($httpsSiteName);
157
		} catch (\Exception $e) {
158
			$this->logger->logException($e, ['app' => 'internet_connection_check']);
159
			return false;
160
		}
161
		return true;
162
	}
163
164
	/**
165
	 * Checks whether a local memcache is installed or not
166
	 * @return bool
167
	 */
168
	private function isMemcacheConfigured() {
169
		return $this->config->getSystemValue('memcache.local', null) !== null;
170
	}
171
172
	/**
173
	 * Whether PHP can generate "secure" pseudorandom integers
174
	 *
175
	 * @return bool
176
	 */
177
	private function isRandomnessSecure() {
178
		try {
179
			$this->secureRandom->generate(1);
180
		} catch (\Exception $ex) {
181
			return false;
182
		}
183
		return true;
184
	}
185
186
	/**
187
	 * Public for the sake of unit-testing
188
	 *
189
	 * @return array
190
	 */
191
	protected function getCurlVersion() {
192
		return curl_version();
193
	}
194
195
	/**
196
	 * Check if the used  SSL lib is outdated. Older OpenSSL and NSS versions do
197
	 * have multiple bugs which likely lead to problems in combination with
198
	 * functionality required by ownCloud such as SNI.
199
	 *
200
	 * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
201
	 * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
202
	 * @return string
203
	 */
204
	private function isUsedTlsLibOutdated() {
205
		// Don't run check when:
206
		// 1. Server has `has_internet_connection` set to false
207
		// 2. AppStore AND S2S is disabled
208
		if(!$this->config->getSystemValue('has_internet_connection', true)) {
209
			return '';
210
		}
211
		if(!$this->config->getSystemValue('appstoreenabled', true)
212
			&& $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
213
			&& $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
214
			return '';
215
		}
216
217
		$versionString = $this->getCurlVersion();
218
		if(isset($versionString['ssl_version'])) {
219
			$versionString = $versionString['ssl_version'];
220
		} else {
221
			return '';
222
		}
223
224
		$features = (string)$this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
225
		if(!$this->config->getSystemValue('appstoreenabled', true)) {
226
			$features = (string)$this->l10n->t('Federated Cloud Sharing');
227
		}
228
229
		// Check if at least OpenSSL after 1.01d or 1.0.2b
230
		if(strpos($versionString, 'OpenSSL/') === 0) {
231
			$majorVersion = substr($versionString, 8, 5);
232
			$patchRelease = substr($versionString, 13, 6);
233
234
			if(($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
235
				($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
236
				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]);
237
			}
238
		}
239
240
		// Check if NSS and perform heuristic check
241
		if(strpos($versionString, 'NSS/') === 0) {
242
			try {
243
				$firstClient = $this->clientService->newClient();
244
				$firstClient->get('https://nextcloud.com/');
245
246
				$secondClient = $this->clientService->newClient();
247
				$secondClient->get('https://nextcloud.com/');
248
			} catch (ClientException $e) {
249
				if($e->getResponse()->getStatusCode() === 400) {
250
					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]);
251
				}
252
			}
253
		}
254
255
		return '';
256
	}
257
258
	/**
259
	 * Whether the version is outdated
260
	 *
261
	 * @return bool
262
	 */
263
	protected function isPhpOutdated() {
264
		if (version_compare(PHP_VERSION, '7.1.0', '<')) {
265
			return true;
266
		}
267
268
		return false;
269
	}
270
271
	/**
272
	 * Whether the php version is still supported (at time of release)
273
	 * according to: https://secure.php.net/supported-versions.php
274
	 *
275
	 * @return array
276
	 */
277
	private function isPhpSupported() {
278
		return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
279
	}
280
281
	/**
282
	 * Check if the reverse proxy configuration is working as expected
283
	 *
284
	 * @return bool
285
	 */
286
	private function forwardedForHeadersWorking() {
287
		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
288
		$remoteAddress = $this->request->getHeader('REMOTE_ADDR');
289
290
		if (\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies)) {
291
			return $remoteAddress !== $this->request->getRemoteAddress();
292
		}
293
		// either not enabled or working correctly
294
		return true;
295
	}
296
297
	/**
298
	 * Checks if the correct memcache module for PHP is installed. Only
299
	 * fails if memcached is configured and the working module is not installed.
300
	 *
301
	 * @return bool
302
	 */
303
	private function isCorrectMemcachedPHPModuleInstalled() {
304
		if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
305
			return true;
306
		}
307
308
		// there are two different memcached modules for PHP
309
		// we only support memcached and not memcache
310
		// https://code.google.com/p/memcached/wiki/PHPClientComparison
311
		return !(!extension_loaded('memcached') && extension_loaded('memcache'));
312
	}
313
314
	/**
315
	 * Checks if set_time_limit is not disabled.
316
	 *
317
	 * @return bool
318
	 */
319
	private function isSettimelimitAvailable() {
320
		if (function_exists('set_time_limit')
321
			&& strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
322
			return true;
323
		}
324
325
		return false;
326
	}
327
328
	/**
329
	 * @return RedirectResponse
330
	 */
331
	public function rescanFailedIntegrityCheck() {
332
		$this->checker->runInstanceVerification();
333
		return new RedirectResponse(
334
			$this->urlGenerator->linkToRoute('settings.AdminSettings.index')
335
		);
336
	}
337
338
	/**
339
	 * @NoCSRFRequired
340
	 * @return DataResponse
341
	 */
342
	public function getFailedIntegrityCheckFiles() {
343
		if(!$this->checker->isCodeCheckEnforced()) {
344
			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...
345
		}
346
347
		$completeResults = $this->checker->getResults();
348
349
		if(!empty($completeResults)) {
350
			$formattedTextResponse = 'Technical information
351
=====================
352
The following list covers which files have failed the integrity check. Please read
353
the previous linked documentation to learn more about the errors and how to fix
354
them.
355
356
Results
357
=======
358
';
359
			foreach($completeResults as $context => $contextResult) {
360
				$formattedTextResponse .= "- $context\n";
361
362
				foreach($contextResult as $category => $result) {
363
					$formattedTextResponse .= "\t- $category\n";
364
					if($category !== 'EXCEPTION') {
365
						foreach ($result as $key => $results) {
366
							$formattedTextResponse .= "\t\t- $key\n";
367
						}
368
					} else {
369
						foreach ($result as $key => $results) {
370
							$formattedTextResponse .= "\t\t- $results\n";
371
						}
372
					}
373
374
				}
375
			}
376
377
			$formattedTextResponse .= '
378
Raw output
379
==========
380
';
381
			$formattedTextResponse .= print_r($completeResults, true);
382
		} else {
383
			$formattedTextResponse = 'No errors have been found.';
384
		}
385
386
387
		$response = new DataDisplayResponse(
388
			$formattedTextResponse,
389
			Http::STATUS_OK,
390
			[
391
				'Content-Type' => 'text/plain',
392
			]
393
		);
394
395
		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...
396
	}
397
398
	/**
399
	 * Checks whether a PHP opcache is properly set up
400
	 * @return bool
401
	 */
402
	protected function isOpcacheProperlySetup() {
403
		$iniWrapper = new IniGetWrapper();
404
405
		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...
406
			return false;
407
		}
408
409
		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...
410
			return false;
411
		}
412
413
		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...
414
			return false;
415
		}
416
417
		if($iniWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
418
			return false;
419
		}
420
421
		if($iniWrapper->getNumeric('opcache.memory_consumption') < 128) {
422
			return false;
423
		}
424
425
		if($iniWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
426
			return false;
427
		}
428
429
		return true;
430
	}
431
432
	/**
433
	 * Check if the required FreeType functions are present
434
	 * @return bool
435
	 */
436
	protected function hasFreeTypeSupport() {
437
		return function_exists('imagettfbbox') && function_exists('imagettftext');
438
	}
439
440
	protected function hasMissingIndexes(): array {
441
		$indexInfo = new MissingIndexInformation();
442
		// Dispatch event so apps can also hint for pending index updates if needed
443
		$event = new GenericEvent($indexInfo);
444
		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
445
446
		return $indexInfo->getListOfMissingIndexes();
447
	}
448
449
	/**
450
	 * warn if outdated version of a memcache module is used
451
	 */
452
	protected function getOutdatedCaches(): array {
453
		$caches = [
454
			'apcu'	=> ['name' => 'APCu', 'version' => '4.0.6'],
455
			'redis'	=> ['name' => 'Redis', 'version' => '2.2.5'],
456
		];
457
		$outdatedCaches = [];
458
		foreach ($caches as $php_module => $data) {
459
			$isOutdated = extension_loaded($php_module) && version_compare(phpversion($php_module), $data['version'], '<');
460
			if ($isOutdated) {
461
				$outdatedCaches[] = $data;
462
			}
463
		}
464
465
		return $outdatedCaches;
466
	}
467
468
	protected function isSqliteUsed() {
469
		return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
470
	}
471
472
	protected function isReadOnlyConfig(): bool {
473
		return \OC_Helper::isReadOnlyConfigEnabled();
474
	}
475
476
	protected function hasValidTransactionIsolationLevel(): bool {
477
		try {
478
			if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
479
				return true;
480
			}
481
482
			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

482
			return $this->db->/** @scrutinizer ignore-call */ getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
Loading history...
483
		} catch (DBALException $e) {
484
			// ignore
485
		}
486
487
		return true;
488
	}
489
490
	protected function hasFileinfoInstalled(): bool {
491
		return \OC_Util::fileInfoLoaded();
492
	}
493
494
	protected function hasWorkingFileLocking(): bool {
495
		return !($this->lockingProvider instanceof NoopLockingProvider);
496
	}
497
498
	protected function getSuggestedOverwriteCliURL(): string {
499
		$suggestedOverwriteCliUrl = '';
500
		if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
501
			$suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
502
			if (!$this->config->getSystemValue('config_is_read_only', false)) {
503
				// Set the overwrite URL when it was not set yet.
504
				$this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
505
				$suggestedOverwriteCliUrl = '';
506
			}
507
		}
508
		return $suggestedOverwriteCliUrl;
509
	}
510
511
	protected function getLastCronInfo(): array {
512
		$lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
513
		return [
514
			'diffInSeconds' => time() - $lastCronRun,
515
			'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

515
			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan(/** @scrutinizer ignore-type */ $lastCronRun),
Loading history...
516
			'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
517
		];
518
	}
519
520
	protected function getCronErrors() {
521
		$errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
522
523
		if (is_array($errors)) {
524
			return $errors;
525
		}
526
527
		return [];
528
	}
529
530
	protected function isPHPMailerUsed(): bool {
531
		return $this->config->getSystemValue('mail_smtpmode', 'smtp') === 'php';
532
	}
533
534
	protected function hasOpcacheLoaded(): bool {
535
		return function_exists('opcache_get_status');
536
	}
537
538
	/**
539
	 * Iterates through the configured app roots and
540
	 * tests if the subdirectories are owned by the same user than the current user.
541
	 *
542
	 * @return array
543
	 */
544
	protected function getAppDirsWithDifferentOwner(): array {
545
		$currentUser = posix_getuid();
546
		$appDirsWithDifferentOwner = [[]];
547
548
		foreach (OC::$APPSROOTS as $appRoot) {
549
			if ($appRoot['writable'] === true) {
550
				$appDirsWithDifferentOwner[] = $this->getAppDirsWithDifferentOwnerForAppRoot($currentUser, $appRoot);
551
			}
552
		}
553
554
		$appDirsWithDifferentOwner = array_merge(...$appDirsWithDifferentOwner);
555
		sort($appDirsWithDifferentOwner);
556
557
		return $appDirsWithDifferentOwner;
558
	}
559
560
	/**
561
	 * Tests if the directories for one apps directory are writable by the current user.
562
	 *
563
	 * @param int $currentUser The current user
564
	 * @param array $appRoot The app root config
565
	 * @return string[] The none writable directory paths inside the app root
566
	 */
567
	private function getAppDirsWithDifferentOwnerForAppRoot(int $currentUser, array $appRoot): array {
568
		$appDirsWithDifferentOwner = [];
569
		$appsPath = $appRoot['path'];
570
		$appsDir = new DirectoryIterator($appRoot['path']);
571
572
		foreach ($appsDir as $fileInfo) {
573
			if ($fileInfo->isDir() && !$fileInfo->isDot()) {
574
				$absAppPath = $appsPath . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
575
				$appDirUser = fileowner($absAppPath);
576
				if ($appDirUser !== $currentUser) {
577
					$appDirsWithDifferentOwner[] = $absAppPath;
578
				}
579
			}
580
		}
581
582
		return $appDirsWithDifferentOwner;
583
	}
584
585
	/**
586
	 * Checks for potential PHP modules that would improve the instance
587
	 *
588
	 * @return string[] A list of PHP modules that is recommended
589
	 */
590
	protected function hasRecommendedPHPModules(): array {
591
		$recommendedPHPModules = [];
592
593
		if (!function_exists('grapheme_strlen')) {
594
			$recommendedPHPModules[] = 'intl';
595
		}
596
597
		if ($this->config->getAppValue('theming', 'enabled', 'no') === 'yes') {
598
			if (!extension_loaded('imagick')) {
599
				$recommendedPHPModules[] = 'imagick';
600
			}
601
		}
602
603
		return $recommendedPHPModules;
604
	}
605
606
	/**
607
	 * @return DataResponse
608
	 */
609
	public function check() {
610
		return new DataResponse(
611
			[
612
				'isGetenvServerWorking' => !empty(getenv('PATH')),
613
				'isReadOnlyConfig' => $this->isReadOnlyConfig(),
614
				'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
615
				'outdatedCaches' => $this->getOutdatedCaches(),
616
				'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
617
				'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
618
				'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
619
				'cronInfo' => $this->getLastCronInfo(),
620
				'cronErrors' => $this->getCronErrors(),
621
				'serverHasInternetConnection' => $this->isInternetConnectionWorking(),
622
				'isMemcacheConfigured' => $this->isMemcacheConfigured(),
623
				'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
624
				'isRandomnessSecure' => $this->isRandomnessSecure(),
625
				'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
626
				'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
627
				'phpSupported' => $this->isPhpSupported(),
628
				'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
629
				'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
630
				'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
631
				'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
632
				'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
633
				'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
634
				'hasOpcacheLoaded' => $this->hasOpcacheLoaded(),
635
				'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
636
				'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
637
				'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
638
				'missingIndexes' => $this->hasMissingIndexes(),
639
				'isSqliteUsed' => $this->isSqliteUsed(),
640
				'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
641
				'isPHPMailerUsed' => $this->isPHPMailerUsed(),
642
				'mailSettingsDocumentation' => $this->urlGenerator->getAbsoluteURL('index.php/settings/admin'),
643
				'isMemoryLimitSufficient' => $this->memoryInfo->isMemoryLimitSufficient(),
644
				'appDirsWithDifferentOwner' => $this->getAppDirsWithDifferentOwner(),
645
				'recommendedPHPModules' => $this->hasRecommendedPHPModules(),
646
			]
647
		);
648
	}
649
}
650