diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js index 6e1c993b3bc7854251b0d781b65c3ed1b0f502d9..eae0abae50c5a151bf00cf04fdf60c1fe2cde281 100644 --- a/core/js/setupchecks.js +++ b/core/js/setupchecks.js @@ -92,6 +92,85 @@ var afterCall = function(data, statusText, xhr) { var messages = []; if (xhr.status === 200 && data) { + if (!data.isGetenvServerWorking) { + messages.push({ + msg: t('core', 'PHP does not seem to be setup properly to query system environment variables. The test with getenv("PATH") only returns an empty response.') + ' ' + + t( + 'core', + 'Please check the <a target="_blank" rel="noreferrer noopener" href="{docLink}">installation documentation ↗</a> for PHP configuration notes and the PHP configuration of your server, especially when using php-fpm.', + { + docLink: oc_defaults.docPlaceholderUrl.replace('PLACEHOLDER', 'admin-php-fpm') + } + ), + type: OC.SetupChecks.MESSAGE_TYPE_WARNING + }); + } + if (data.isReadOnlyConfig) { + messages.push({ + msg: t('core', 'The read-only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update.'), + type: OC.SetupChecks.MESSAGE_TYPE_INFO + }); + } + if (!data.hasValidTransactionIsolationLevel) { + messages.push({ + msg: t('core', 'Your database does not run with "READ COMMITTED" transaction isolation level. This can cause problems when multiple actions are executed in parallel.'), + type: OC.SetupChecks.MESSAGE_TYPE_ERROR + }); + } + if(!data.hasFileinfoInstalled) { + messages.push({ + msg: t('core', 'The PHP module "fileinfo" is missing. It is strongly recommended to enable this module to get the best results with MIME type detection.'), + type: OC.SetupChecks.MESSAGE_TYPE_INFO + }); + } + if (data.outdatedCaches.length > 0) { + data.outdatedCaches.forEach(function(element){ + messages.push({ + msg: t( + 'core', + '{name} below version {version} is installed, for stability and performance reasons it is recommended to update to a newer {name} version.', + element + ), + type: OC.SetupChecks.MESSAGE_TYPE_WARNING + }) + }); + } + if(!data.hasWorkingFileLocking) { + messages.push({ + msg: t('core', 'Transactional file locking is disabled, this might lead to issues with race conditions. Enable "filelocking.enabled" in config.php to avoid these problems. See the <a target="_blank" rel="noreferrer noopener" href="{docLink}">documentation ↗</a> for more information.', {docLink: oc_defaults.docPlaceholderUrl.replace('PLACEHOLDER', 'admin-transactional-locking')}), + type: OC.SetupChecks.MESSAGE_TYPE_WARNING + }); + } + if (data.suggestedOverwriteCliURL !== '') { + messages.push({ + msg: t('core', 'If your installation is not installed at the root of the domain and uses system cron, there can be issues with the URL generation. To avoid these problems, please set the "overwrite.cli.url" option in your config.php file to the webroot path of your installation (suggestion: "{suggestedOverwriteCliURL}")', {suggestedOverwriteCliURL: data.suggestedOverwriteCliURL}), + type: OC.SetupChecks.MESSAGE_TYPE_WARNING + }); + } + if (data.cronErrors.length > 0) { + var listOfCronErrors = ""; + data.cronErrors.forEach(function(element){ + listOfCronErrors += "<li>"; + listOfCronErrors += element.error; + listOfCronErrors += ' '; + listOfCronErrors += element.hint; + listOfCronErrors += "</li>"; + }); + messages.push({ + msg: t( + 'core', + 'It was not possible to execute the cron job via CLI. The following technical errors have appeared:' + ) + "<ul>" + listOfCronErrors + "</ul>", + type: OC.SetupChecks.MESSAGE_TYPE_ERROR + }) + } + if (data.cronInfo.diffInSeconds > 3600) { + messages.push({ + msg: t('core', 'Last background job execution ran {relativeTime}. Something seems wrong.', {relativeTime: data.cronInfo.relativeTime}) + + ' <a href="' + data.cronInfo.backgroundJobsUrl + '">' + t('core', 'Check the background job settings') + '</a>', + type: OC.SetupChecks.MESSAGE_TYPE_ERROR + }); + } if (!data.serverHasInternetConnection) { messages.push({ msg: t('core', 'This server has no working Internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the Internet to enjoy all features.'), @@ -183,9 +262,9 @@ type: OC.SetupChecks.MESSAGE_TYPE_INFO }) } - if (data.hasMissingIndexes.length > 0) { + if (data.missingIndexes.length > 0) { var listOfMissingIndexes = ""; - data.hasMissingIndexes.forEach(function(element){ + data.missingIndexes.forEach(function(element){ listOfMissingIndexes += "<li>"; listOfMissingIndexes += t('core', 'Missing index "{indexName}" in table "{tableName}".', element); listOfMissingIndexes += "</li>"; diff --git a/core/js/tests/specs/setupchecksSpec.js b/core/js/tests/specs/setupchecksSpec.js index 316b5d4c592f5b40a0a78afb72f91ff75a231870..900b9f8fc660c638c11cb5bf4c6f18fe85c366b5 100644 --- a/core/js/tests/specs/setupchecksSpec.js +++ b/core/js/tests/specs/setupchecksSpec.js @@ -149,6 +149,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json' }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: true, serverHasInternetConnection: false, memcacheDocs: 'https://docs.nextcloud.com/server/go.php?to=admin-performance', @@ -158,7 +164,12 @@ describe('OC.SetupChecks tests', function() { isOpcacheProperlySetup: true, isSettimelimitAvailable: true, hasFreeTypeSupport: true, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); @@ -184,6 +195,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json' }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: true, serverHasInternetConnection: false, memcacheDocs: 'https://docs.nextcloud.com/server/go.php?to=admin-performance', @@ -193,7 +210,12 @@ describe('OC.SetupChecks tests', function() { isOpcacheProperlySetup: true, isSettimelimitAvailable: true, hasFreeTypeSupport: true, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); @@ -220,6 +242,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: true, serverHasInternetConnection: false, isMemcacheConfigured: true, @@ -229,7 +257,12 @@ describe('OC.SetupChecks tests', function() { isOpcacheProperlySetup: true, isSettimelimitAvailable: true, hasFreeTypeSupport: true, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); @@ -253,6 +286,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: false, securityDocs: 'https://docs.owncloud.org/myDocs.html', serverHasInternetConnection: true, @@ -263,7 +302,12 @@ describe('OC.SetupChecks tests', function() { isOpcacheProperlySetup: true, isSettimelimitAvailable: true, hasFreeTypeSupport: true, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); @@ -285,6 +329,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: true, securityDocs: 'https://docs.owncloud.org/myDocs.html', serverHasInternetConnection: true, @@ -295,7 +345,12 @@ describe('OC.SetupChecks tests', function() { isOpcacheProperlySetup: true, isSettimelimitAvailable: true, hasFreeTypeSupport: true, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); @@ -317,6 +372,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: true, serverHasInternetConnection: true, isMemcacheConfigured: true, @@ -327,7 +388,12 @@ describe('OC.SetupChecks tests', function() { isOpcacheProperlySetup: true, isSettimelimitAvailable: true, hasFreeTypeSupport: true, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); @@ -349,6 +415,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: true, serverHasInternetConnection: true, isMemcacheConfigured: true, @@ -359,7 +431,12 @@ describe('OC.SetupChecks tests', function() { isOpcacheProperlySetup: true, isSettimelimitAvailable: false, hasFreeTypeSupport: true, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); @@ -401,6 +478,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json', }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: true, securityDocs: 'https://docs.owncloud.org/myDocs.html', serverHasInternetConnection: true, @@ -412,7 +495,12 @@ describe('OC.SetupChecks tests', function() { isOpcacheProperlySetup: true, isSettimelimitAvailable: true, hasFreeTypeSupport: true, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); @@ -434,6 +522,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json' }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: true, securityDocs: 'https://docs.owncloud.org/myDocs.html', serverHasInternetConnection: true, @@ -445,7 +539,12 @@ describe('OC.SetupChecks tests', function() { phpOpcacheDocumentation: 'https://example.org/link/to/doc', isSettimelimitAvailable: true, hasFreeTypeSupport: true, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); @@ -467,6 +566,12 @@ describe('OC.SetupChecks tests', function() { 'Content-Type': 'application/json' }, JSON.stringify({ + hasFileinfoInstalled: true, + isGetenvServerWorking: true, + isReadOnlyConfig: false, + hasWorkingFileLocking: true, + hasValidTransactionIsolationLevel: true, + suggestedOverwriteCliURL: '', isUrandomAvailable: true, securityDocs: 'https://docs.owncloud.org/myDocs.html', serverHasInternetConnection: true, @@ -478,7 +583,12 @@ describe('OC.SetupChecks tests', function() { phpOpcacheDocumentation: 'https://example.org/link/to/doc', isSettimelimitAvailable: true, hasFreeTypeSupport: false, - hasMissingIndexes: [] + missingIndexes: [], + outdatedCaches: [], + cronErrors: [], + cronInfo: { + diffInSeconds: 0 + } }) ); diff --git a/lib/private/Settings/Admin/Overview.php b/lib/private/Settings/Admin/Overview.php index 6e186dc6f9872f380c2d55112f414b1f34ae2ad3..51e5808f487a271a3dba23c875b096143e4212fc 100644 --- a/lib/private/Settings/Admin/Overview.php +++ b/lib/private/Settings/Admin/Overview.php @@ -23,112 +23,24 @@ namespace OC\Settings\Admin; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\DBALException; -use Doctrine\DBAL\Platforms\SqlitePlatform; -use OC\Lock\DBLockingProvider; -use OC\Lock\NoopLockingProvider; use OCP\AppFramework\Http\TemplateResponse; use OCP\IConfig; -use OCP\IDBConnection; -use OCP\IL10N; -use OCP\IRequest; -use OCP\Lock\ILockingProvider; use OCP\Settings\ISettings; class Overview implements ISettings { - /** @var IDBConnection|Connection */ - private $db; - /** @var IRequest */ - private $request; /** @var IConfig */ private $config; - /** @var ILockingProvider */ - private $lockingProvider; - /** @var IL10N */ - private $l; - /** - * @param IDBConnection $db - * @param IRequest $request - * @param IConfig $config - * @param ILockingProvider $lockingProvider - * @param IL10N $l - */ - public function __construct(IDBConnection $db, - IRequest $request, - IConfig $config, - ILockingProvider $lockingProvider, - IL10N $l) { - $this->db = $db; - $this->request = $request; + public function __construct(IConfig $config) { $this->config = $config; - $this->lockingProvider = $lockingProvider; - $this->l = $l; } /** * @return TemplateResponse */ public function getForm() { - try { - if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) { - $invalidTransactionIsolationLevel = false; - } else { - $invalidTransactionIsolationLevel = $this->db->getTransactionIsolation() !== Connection::TRANSACTION_READ_COMMITTED; - } - } catch (DBALException $e) { - // ignore - $invalidTransactionIsolationLevel = false; - } - - $envPath = getenv('PATH'); - - // warn if outdated version of a memcache module is used - $caches = [ - 'apcu' => ['name' => $this->l->t('APCu'), 'version' => '4.0.6'], - 'redis' => ['name' => $this->l->t('Redis'), 'version' => '2.2.5'], - ]; - $outdatedCaches = []; - foreach ($caches as $php_module => $data) { - $isOutdated = extension_loaded($php_module) && version_compare(phpversion($php_module), $data['version'], '<'); - if ($isOutdated) { - $outdatedCaches[$php_module] = $data; - } - } - - if ($this->lockingProvider instanceof NoopLockingProvider) { - $fileLockingType = 'none'; - } else if ($this->lockingProvider instanceof DBLockingProvider) { - $fileLockingType = 'db'; - } else { - $fileLockingType = 'cache'; - } - - $suggestedOverwriteCliUrl = ''; - if ($this->config->getSystemValue('overwrite.cli.url', '') === '') { - $suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT; - if (!$this->config->getSystemValue('config_is_read_only', false)) { - // Set the overwrite URL when it was not set yet. - $this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl); - $suggestedOverwriteCliUrl = ''; - } - } - $parameters = [ - // Diagnosis - 'readOnlyConfigEnabled' => \OC_Helper::isReadOnlyConfigEnabled(), - 'isLocaleWorking' => \OC_Util::isSetLocaleWorking(), - 'isAnnotationsWorking' => \OC_Util::isAnnotationsWorking(), 'checkForWorkingWellKnownSetup' => $this->config->getSystemValue('check_for_working_wellknown_setup', true), - 'has_fileinfo' => \OC_Util::fileInfoLoaded(), - 'invalidTransactionIsolationLevel' => $invalidTransactionIsolationLevel, - 'getenvServerNotWorking' => empty($envPath), - 'OutdatedCacheWarning' => $outdatedCaches, - 'fileLockingType' => $fileLockingType, - 'suggestedOverwriteCliUrl' => $suggestedOverwriteCliUrl, - 'lastcron' => $this->config->getAppValue('core', 'lastcron', false), - 'cronErrors' => $this->config->getAppValue('core', 'cronErrors'), ]; return new TemplateResponse('settings', 'settings/admin/overview', $parameters, ''); diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php index bfccdf392bb6617b394d4adeea48af6c23547042..a974eb9808d628fbc71ac01600c6dce9712f03fd 100644 --- a/lib/private/Settings/Manager.php +++ b/lib/private/Settings/Manager.php @@ -259,7 +259,7 @@ class Manager implements IManager { if ($section === 'overview') { /** @var ISettings $form */ - $form = new Admin\Overview($this->dbc, $this->request, $this->config, $this->lockingProvider, $this->l); + $form = new Admin\Overview($this->config); $forms[$form->getPriority()] = [$form]; $form = new Admin\ServerDevNotice(); $forms[$form->getPriority()] = [$form]; diff --git a/settings/Controller/CheckSetupController.php b/settings/Controller/CheckSetupController.php index b4619ee4bb0e625b63af4c37dfe56037e2ee6b76..f83d0966edab74372222a908c3a22b30dc560dfa 100644 --- a/settings/Controller/CheckSetupController.php +++ b/settings/Controller/CheckSetupController.php @@ -31,21 +31,27 @@ namespace OC\Settings\Controller; use bantu\IniGetWrapper\IniGetWrapper; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Platforms\SqlitePlatform; use GuzzleHttp\Exception\ClientException; use OC\AppFramework\Http; +use OC\DB\Connection; use OC\DB\MissingIndexInformation; use OC\IntegrityCheck\Checker; +use OC\Lock\NoopLockingProvider; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataDisplayResponse; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\RedirectResponse; use OCP\Http\Client\IClientService; use OCP\IConfig; +use OCP\IDateTimeFormatter; use OCP\IDBConnection; use OCP\IL10N; use OCP\ILogger; use OCP\IRequest; use OCP\IURLGenerator; +use OCP\Lock\ILockingProvider; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -69,6 +75,12 @@ class CheckSetupController extends Controller { private $logger; /** @var EventDispatcherInterface */ private $dispatcher; + /** @var IDBConnection|Connection */ + private $db; + /** @var ILockingProvider */ + private $lockingProvider; + /** @var IDateTimeFormatter */ + private $dateTimeFormatter; public function __construct($AppName, IRequest $request, @@ -79,7 +91,10 @@ class CheckSetupController extends Controller { IL10N $l10n, Checker $checker, ILogger $logger, - EventDispatcherInterface $dispatcher) { + EventDispatcherInterface $dispatcher, + IDBConnection $db, + ILockingProvider $lockingProvider, + IDateTimeFormatter $dateTimeFormatter) { parent::__construct($AppName, $request); $this->config = $config; $this->clientService = $clientService; @@ -89,6 +104,9 @@ class CheckSetupController extends Controller { $this->checker = $checker; $this->logger = $logger; $this->dispatcher = $dispatcher; + $this->db = $db; + $this->lockingProvider = $lockingProvider; + $this->dateTimeFormatter = $dateTimeFormatter; } /** @@ -424,16 +442,92 @@ Raw output return $indexInfo->getListOfMissingIndexes(); } + /** + * warn if outdated version of a memcache module is used + */ + protected function getOutdatedCaches(): array { + $caches = [ + 'apcu' => ['name' => 'APCu', 'version' => '4.0.6'], + 'redis' => ['name' => 'Redis', 'version' => '2.2.5'], + ]; + $outdatedCaches = []; + foreach ($caches as $php_module => $data) { + $isOutdated = extension_loaded($php_module) && version_compare(phpversion($php_module), $data['version'], '<'); + if ($isOutdated) { + $outdatedCaches[] = $data; + } + } + + return $outdatedCaches; + } + protected function isSqliteUsed() { return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false; } + protected function isReadOnlyConfig(): bool { + return \OC_Helper::isReadOnlyConfigEnabled(); + } + + protected function hasValidTransactionIsolationLevel(): bool { + try { + if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) { + return true; + } + + return $this->db->getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED; + } catch (DBALException $e) { + // ignore + } + + return true; + } + + protected function hasFileinfoInstalled(): bool { + return \OC_Util::fileInfoLoaded(); + } + + protected function hasWorkingFileLocking(): bool { + return !($this->lockingProvider instanceof NoopLockingProvider); + } + + protected function getSuggestedOverwriteCliURL(): string { + $suggestedOverwriteCliUrl = ''; + if ($this->config->getSystemValue('overwrite.cli.url', '') === '') { + $suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT; + if (!$this->config->getSystemValue('config_is_read_only', false)) { + // Set the overwrite URL when it was not set yet. + $this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl); + $suggestedOverwriteCliUrl = ''; + } + } + return $suggestedOverwriteCliUrl; + } + + protected function getLastCronInfo(): array { + $lastCronRun = $this->config->getAppValue('core', 'lastcron', 0); + return [ + 'diffInSeconds' => time() - $lastCronRun, + 'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun), + 'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs', + ]; + } + /** * @return DataResponse */ public function check() { return new DataResponse( [ + 'isGetenvServerWorking' => !empty(getenv('PATH')), + 'isReadOnlyConfig' => $this->isReadOnlyConfig(), + 'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(), + 'outdatedCaches' => $this->getOutdatedCaches(), + 'hasFileinfoInstalled' => $this->hasFileinfoInstalled(), + 'hasWorkingFileLocking' => $this->hasWorkingFileLocking(), + 'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(), + 'cronInfo' => $this->getLastCronInfo(), + 'cronErrors' => json_decode($this->config->getAppValue('core', 'cronErrors', ''), true), 'serverHasInternetConnection' => $this->isInternetConnectionWorking(), 'isMemcacheConfigured' => $this->isMemcacheConfigured(), 'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'), @@ -450,7 +544,7 @@ Raw output 'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'), 'isSettimelimitAvailable' => $this->isSettimelimitAvailable(), 'hasFreeTypeSupport' => $this->hasFreeTypeSupport(), - 'hasMissingIndexes' => $this->hasMissingIndexes(), + 'missingIndexes' => $this->hasMissingIndexes(), 'isSqliteUsed' => $this->isSqliteUsed(), 'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'), ] diff --git a/settings/css/settings.scss b/settings/css/settings.scss index 5c1714021fb857e2f984e8294ab2c44e4b489955..a6f93a3e95d0bdb89e1cbf21b52aaaadcbb117db 100644 --- a/settings/css/settings.scss +++ b/settings/css/settings.scss @@ -1319,10 +1319,6 @@ doesnotexist:-o-prefocus, .strengthify-wrapper { text-decoration: underline; } - & > ul { - color: $color-error; - } - .extra-top-margin { margin-top: 12px; } diff --git a/settings/templates/settings/admin/overview.php b/settings/templates/settings/admin/overview.php index 5fb5e110eb1f141dcbb767b64136813289d84152..cf725d3101e808be63a373ca9ea69f4b75711f8b 100644 --- a/settings/templates/settings/admin/overview.php +++ b/settings/templates/settings/admin/overview.php @@ -23,133 +23,13 @@ /** @var \OCP\IL10N $l */ /** @var array $_ */ +/** @var \OCP\Defaults $theme */ ?> <div id="security-warning" class="section"> <h2><?php p($l->t('Security & setup warnings'));?></h2> <p class="settings-hint"><?php p($l->t('It\'s important for the security and performance of your instance that everything is configured correctly. To help you with that we are doing some automatic checks. Please see the linked documentation for more information.'));?></p> - <ul> - <?php - // is php setup properly to query system environment variables like getenv('PATH') - if ($_['getenvServerNotWorking']) { - ?> - <li> - <?php p($l->t('PHP does not seem to be setup properly to query system environment variables. The test with getenv("PATH") only returns an empty response.')); ?><br> - <?php print_unescaped($l->t('Please check the <a target="_blank" rel="noreferrer noopener" href="%s">installation documentation ↗</a> for PHP configuration notes and the PHP configuration of your server, especially when using php-fpm.', link_to_docs('admin-php-fpm'))); ?> - </li> - <?php - } - - // is read only config enabled - if ($_['readOnlyConfigEnabled']) { - ?> - <li> - <?php p($l->t('The Read-Only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update.')); ?> - </li> - <?php - } - - // Are doc blocks accessible? - if (!$_['isAnnotationsWorking']) { - ?> - <li> - <?php p($l->t('PHP is apparently set up to strip inline doc blocks. This will make several core apps inaccessible.')); ?><br> - <?php p($l->t('This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.')); ?> - </li> - <?php - } - - // Is the Transaction isolation level READ_COMMITTED? - if ($_['invalidTransactionIsolationLevel']) { - ?> - <li> - <?php p($l->t('Your database does not run with "READ COMMITTED" transaction isolation level. This can cause problems when multiple actions are executed in parallel.')); ?> - </li> - <?php - } - - // Warning if memcache is outdated - foreach ($_['OutdatedCacheWarning'] as $php_module => $data) { - ?> - <li> - <?php p($l->t('%1$s below version %2$s is installed, for stability and performance reasons it is recommended to update to a newer %1$s version.', $data)); ?> - </li> - <?php - } - - // if module fileinfo available? - if (!$_['has_fileinfo']) { - ?> - <li> - <?php p($l->t('The PHP module \'fileinfo\' is missing. It is strongly recommended to enable this module to get the best results with MIME type detection.')); ?> - </li> - <?php - } - - // locking configured optimally? - if ($_['fileLockingType'] === 'none') { - ?> - <li> - <?php print_unescaped($l->t('Transactional file locking is disabled, this might lead to issues with race conditions. Enable \'filelocking.enabled\' in config.php to avoid these problems. See the <a target="_blank" rel="noreferrer noopener" href="%s">documentation ↗</a> for more information.', link_to_docs('admin-transactional-locking'))); ?> - </li> - <?php - } - - // is locale working ? - if (!$_['isLocaleWorking']) { - ?> - <li> - <?php - $locales = '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'; - p($l->t('System locale can not be set to a one which supports UTF-8.')); - ?> - <br> - <?php - p($l->t('This means that there might be problems with certain characters in filenames.')); - ?> - <br> - <?php - p($l->t('It is strongly proposed to install the required packages on your system to support one of the following locales: %s.', [$locales])); - ?> - </li> - <?php - } - - if ($_['suggestedOverwriteCliUrl']) { - ?> - <li> - <?php p($l->t('If your installation is not installed at the root of the domain and uses system Cron, there can be issues with the URL generation. To avoid these problems, please set the "overwrite.cli.url" option in your config.php file to the webroot path of your installation (Suggested: "%s")', $_['suggestedOverwriteCliUrl'])); ?> - </li> - <?php - } - - if ($_['cronErrors']) { - ?> - <li> - <?php p($l->t('It was not possible to execute the cron job via CLI. The following technical errors have appeared:')); ?> - <br> - <ol> - <?php foreach(json_decode($_['cronErrors']) as $error) { if(isset($error->error)) {?> - <li><?php p($error->error) ?> <?php p($error->hint) ?></li> - <?php }} ?> - </ol> - </li> - <?php - } - ?> - <?php if ($_['lastcron'] !== false): - $relative_time = relative_modified_date($_['lastcron']); - $formatter = \OC::$server->getDateTimeFormatter(); - $absolute_time = $formatter->formatDateTime($_['lastcron'], 'long', 'long'); - if (time() - $_['lastcron'] > 3600): ?> - <li class="crondate" title="<?php p($absolute_time);?>"> - <?php p($l->t("Last background job execution ran %s. Something seems wrong.", [$relative_time]));?> - <a href="<?php p(\OC::$server->getURLGenerator()->linkToRoute('settings.AdminSettings.index', ['section' => 'server'])); ?>#backgroundjobs"><?php p($l->t('Check the background job settings')); ?></a> - </li> - <?php endif; ?> - <?php endif; ?> - </ul> <div id="security-warning-state-ok" class="hidden"> <span class="icon icon-checkmark-white"></span><span class="message"><?php p($l->t('All checks passed.'));?></span> diff --git a/tests/Settings/Controller/CheckSetupControllerTest.php b/tests/Settings/Controller/CheckSetupControllerTest.php index f0e19e007f21a5bd8c9e027372170af3fccee1fa..c062dff0704366db6c2acfaf705bfc17b38a6169 100644 --- a/tests/Settings/Controller/CheckSetupControllerTest.php +++ b/tests/Settings/Controller/CheckSetupControllerTest.php @@ -21,6 +21,7 @@ namespace Tests\Settings\Controller; +use OC\DB\Connection; use OC\Settings\Controller\CheckSetupController; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataDisplayResponse; @@ -28,11 +29,13 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\RedirectResponse; use OCP\Http\Client\IClientService; use OCP\IConfig; +use OCP\IDateTimeFormatter; use OCP\IL10N; use OCP\ILogger; use OCP\IRequest; use OCP\IURLGenerator; use OC_Util; +use OCP\Lock\ILockingProvider; use Psr\Http\Message\ResponseInterface; use Symfony\Component\EventDispatcher\EventDispatcher; use Test\TestCase; @@ -64,6 +67,12 @@ class CheckSetupControllerTest extends TestCase { private $checker; /** @var EventDispatcher|\PHPUnit_Framework_MockObject_MockObject */ private $dispatcher; + /** @var Connection|\PHPUnit_Framework_MockObject_MockObject */ + private $db; + /** @var ILockingProvider|\PHPUnit_Framework_MockObject_MockObject */ + private $lockingProvider; + /** @var IDateTimeFormatter|\PHPUnit_Framework_MockObject_MockObject */ + private $dateTimeFormatter; public function setUp() { parent::setUp(); @@ -90,6 +99,10 @@ class CheckSetupControllerTest extends TestCase { $this->checker = $this->getMockBuilder('\OC\IntegrityCheck\Checker') ->disableOriginalConstructor()->getMock(); $this->logger = $this->getMockBuilder(ILogger::class)->getMock(); + $this->db = $this->getMockBuilder(Connection::class) + ->disableOriginalConstructor()->getMock(); + $this->lockingProvider = $this->getMockBuilder(ILockingProvider::class)->getMock(); + $this->dateTimeFormatter = $this->getMockBuilder(IDateTimeFormatter::class)->getMock(); $this->checkSetupController = $this->getMockBuilder('\OC\Settings\Controller\CheckSetupController') ->setConstructorArgs([ 'settings', @@ -102,8 +115,11 @@ class CheckSetupControllerTest extends TestCase { $this->checker, $this->logger, $this->dispatcher, + $this->db, + $this->lockingProvider, + $this->dateTimeFormatter, ]) - ->setMethods(['getCurlVersion', 'isPhpOutdated', 'isOpcacheProperlySetup', 'hasFreeTypeSupport', 'hasMissingIndexes', 'isSqliteUsed'])->getMock(); + ->setMethods(['isReadOnlyConfig', 'hasValidTransactionIsolationLevel', 'hasFileinfoInstalled', 'hasWorkingFileLocking', 'getLastCronInfo', 'getSuggestedOverwriteCliURL', 'getOutdatedCaches', 'getCurlVersion', 'isPhpOutdated', 'isOpcacheProperlySetup', 'hasFreeTypeSupport', 'hasMissingIndexes', 'isSqliteUsed'])->getMock(); } public function testIsInternetConnectionWorkingDisabledViaConfig() { @@ -263,21 +279,21 @@ class CheckSetupControllerTest extends TestCase { public function testCheck() { $this->config->expects($this->at(0)) - ->method('getSystemValue') - ->with('has_internet_connection', true) - ->will($this->returnValue(true)); - $this->config->expects($this->at(1)) + ->method('getAppValue') + ->with('core', 'cronErrors') + ->willReturn(''); + $this->config->expects($this->at(2)) ->method('getSystemValue') ->with('memcache.local', null) ->will($this->returnValue('SomeProvider')); - $this->config->expects($this->at(2)) + $this->config->expects($this->at(3)) ->method('getSystemValue') ->with('has_internet_connection', true) - ->will($this->returnValue(false)); - $this->config->expects($this->at(3)) + ->will($this->returnValue(true)); + $this->config->expects($this->at(4)) ->method('getSystemValue') - ->with('trusted_proxies', []) - ->willReturn(['1.2.3.4']); + ->with('appstoreenabled', true) + ->will($this->returnValue(false)); $this->request->expects($this->once()) ->method('getRemoteAddress') @@ -342,12 +358,56 @@ class CheckSetupControllerTest extends TestCase { $this->checkSetupController ->method('hasMissingIndexes') ->willReturn([]); + $this->checkSetupController + ->method('getOutdatedCaches') + ->willReturn([]); $this->checkSetupController ->method('isSqliteUsed') ->willReturn(false); + $this->checkSetupController + ->expects($this->once()) + ->method('isReadOnlyConfig') + ->willReturn(false); + $this->checkSetupController + ->expects($this->once()) + ->method('hasValidTransactionIsolationLevel') + ->willReturn(true); + $this->checkSetupController + ->expects($this->once()) + ->method('hasFileinfoInstalled') + ->willReturn(true); + $this->checkSetupController + ->expects($this->once()) + ->method('hasWorkingFileLocking') + ->willReturn(true); + $this->checkSetupController + ->expects($this->once()) + ->method('getSuggestedOverwriteCliURL') + ->willReturn(''); + $this->checkSetupController + ->expects($this->once()) + ->method('getLastCronInfo') + ->willReturn([ + 'diffInSeconds' => 123, + 'relativeTime' => '2 hours ago', + 'backgroundJobsUrl' => 'https://example.org', + ]); $expected = new DataResponse( [ + 'isGetenvServerWorking' => true, + 'isReadOnlyConfig' => false, + 'hasValidTransactionIsolationLevel' => true, + 'outdatedCaches' => [], + 'hasFileinfoInstalled' => true, + 'hasWorkingFileLocking' => true, + 'suggestedOverwriteCliURL' => '', + 'cronInfo' => [ + 'diffInSeconds' => 123, + 'relativeTime' => '2 hours ago', + 'backgroundJobsUrl' => 'https://example.org', + ], + 'cronErrors' => '', 'serverHasInternetConnection' => false, 'isMemcacheConfigured' => true, 'memcacheDocs' => 'http://docs.example.org/server/go.php?to=admin-performance', @@ -367,9 +427,9 @@ class CheckSetupControllerTest extends TestCase { 'phpOpcacheDocumentation' => 'http://docs.example.org/server/go.php?to=admin-php-opcache', 'isSettimelimitAvailable' => true, 'hasFreeTypeSupport' => false, - 'hasMissingIndexes' => [], 'isSqliteUsed' => false, 'databaseConversionDocumentation' => 'http://docs.example.org/server/go.php?to=admin-db-conversion', + 'missingIndexes' => [], ] ); $this->assertEquals($expected, $this->checkSetupController->check()); @@ -388,6 +448,9 @@ class CheckSetupControllerTest extends TestCase { $this->checker, $this->logger, $this->dispatcher, + $this->db, + $this->lockingProvider, + $this->dateTimeFormatter, ]) ->setMethods(null)->getMock();