cbc441ba9173ff42b8c99a57013a251c7f948a83
[Packages/TYPO3.CMS.git] / typo3 / sysext / reports / Classes / Report / Status / ConfigurationStatus.php
1 <?php
2 namespace TYPO3\CMS\Reports\Report\Status;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
20 use TYPO3\CMS\Core\Localization\LanguageService;
21 use TYPO3\CMS\Core\Messaging\FlashMessage;
22 use TYPO3\CMS\Core\Messaging\FlashMessageService;
23 use TYPO3\CMS\Core\Registry;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Core\Utility\PathUtility;
26 use TYPO3\CMS\Reports\Status as ReportStatus;
27 use TYPO3\CMS\Reports\StatusProviderInterface;
28
29 /**
30 * Performs some checks about the install tool protection status
31 */
32 class ConfigurationStatus implements StatusProviderInterface
33 {
34 /**
35 * 10MB
36 *
37 * @var int
38 */
39 protected $deprecationLogFileSizeWarningThreshold = 10485760;
40
41 /**
42 * 100MB
43 *
44 * @var int
45 */
46 protected $deprecationLogFileSizeErrorThreshold = 104857600;
47
48 /**
49 * Determines the Install Tool's status, mainly concerning its protection.
50 *
51 * @return array List of statuses
52 */
53 public function getStatus()
54 {
55 $this->executeAdminCommand();
56 $statuses = [
57 'emptyReferenceIndex' => $this->getReferenceIndexStatus(),
58 'deprecationLog' => $this->getDeprecationLogStatus()
59 ];
60 if ($this->isMemcachedUsed()) {
61 $statuses['memcachedConnection'] = $this->getMemcachedConnectionStatus();
62 }
63 if (TYPO3_OS !== 'WIN') {
64 $statuses['createdFilesWorldWritable'] = $this->getCreatedFilesWorldWritableStatus();
65 $statuses['createdDirectoriesWorldWritable'] = $this->getCreatedDirectoriesWorldWritableStatus();
66 }
67 if ($this->isMysqlUsed()) {
68 $statuses['mysqlDatabaseUsesUtf8'] = $this->getMysqlDatabaseUtf8Status();
69 }
70 return $statuses;
71 }
72
73 /**
74 * Checks if sys_refindex is empty.
75 *
76 * @return \TYPO3\CMS\Reports\Status An object representing whether the reference index is empty or not
77 */
78 protected function getReferenceIndexStatus()
79 {
80 $value = $this->getLanguageService()->getLL('status_ok');
81 $message = '';
82 $severity = ReportStatus::OK;
83
84 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
85 $count = $queryBuilder
86 ->count('*')
87 ->from('sys_refindex')
88 ->execute()
89 ->fetchColumn(0);
90
91 $registry = GeneralUtility::makeInstance(Registry::class);
92 $lastRefIndexUpdate = $registry->get('core', 'sys_refindex_lastUpdate');
93 if (!$count && $lastRefIndexUpdate) {
94 $value = $this->getLanguageService()->getLL('status_empty');
95 $severity = ReportStatus::WARNING;
96 $url = BackendUtility::getModuleUrl('system_dbint') . '&id=0&SET[function]=refindex';
97 $message = sprintf($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.backend_reference_index'), '<a href="' . htmlspecialchars($url) . '">', '</a>', BackendUtility::datetime($lastRefIndexUpdate));
98 }
99 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_referenceIndex'), $value, $message, $severity);
100 }
101
102 /**
103 * Checks whether memcached is configured, if that's the case we assume it's also used.
104 *
105 * @return bool TRUE if memcached is used, FALSE otherwise.
106 */
107 protected function isMemcachedUsed()
108 {
109 $memcachedUsed = false;
110 $memcachedServers = $this->getConfiguredMemcachedServers();
111 if (!empty($memcachedServers)) {
112 $memcachedUsed = true;
113 }
114 return $memcachedUsed;
115 }
116
117 /**
118 * Gets the configured memcached server connections.
119 *
120 * @return array An array of configured memcached server connections.
121 */
122 protected function getConfiguredMemcachedServers()
123 {
124 $memcachedServers = [];
125 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'])) {
126 foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] as $table => $conf) {
127 if (is_array($conf)) {
128 foreach ($conf as $key => $value) {
129 if (!is_array($value) && $value === \TYPO3\CMS\Core\Cache\Backend\MemcachedBackend::class) {
130 $memcachedServers = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$table]['options']['servers'];
131 break;
132 }
133 }
134 }
135 }
136 }
137 return $memcachedServers;
138 }
139
140 /**
141 * Checks whether TYPO3 can connect to the configured memcached servers.
142 *
143 * @return \TYPO3\CMS\Reports\Status An object representing whether TYPO3 can connect to the configured memcached servers
144 */
145 protected function getMemcachedConnectionStatus()
146 {
147 $value = $this->getLanguageService()->getLL('status_ok');
148 $message = '';
149 $severity = ReportStatus::OK;
150 $failedConnections = [];
151 $defaultMemcachedPort = ini_get('memcache.default_port');
152 $memcachedServers = $this->getConfiguredMemcachedServers();
153 if (function_exists('memcache_connect') && is_array($memcachedServers)) {
154 foreach ($memcachedServers as $testServer) {
155 $configuredServer = $testServer;
156 if (substr($testServer, 0, 7) === 'unix://') {
157 $host = $testServer;
158 $port = 0;
159 } else {
160 if (substr($testServer, 0, 6) === 'tcp://') {
161 $testServer = substr($testServer, 6);
162 }
163 if (strstr($testServer, ':') !== false) {
164 list($host, $port) = explode(':', $testServer, 2);
165 } else {
166 $host = $testServer;
167 $port = $defaultMemcachedPort;
168 }
169 }
170 $memcachedConnection = @memcache_connect($host, $port);
171 if ($memcachedConnection != null) {
172 memcache_close($memcachedConnection);
173 } else {
174 $failedConnections[] = $configuredServer;
175 }
176 }
177 }
178 if (!empty($failedConnections)) {
179 $value = $this->getLanguageService()->getLL('status_connectionFailed');
180 $severity = ReportStatus::WARNING;
181 $message = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:warning.memcache_not_usable') . '<br /><br />' . '<ul><li>' . implode('</li><li>', $failedConnections) . '</li></ul>';
182 }
183 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_memcachedConfiguration'), $value, $message, $severity);
184 }
185
186 /**
187 * Provides status information on the deprecation log, whether it's enabled
188 * and if so whether certain limits in file size are reached.
189 *
190 * @return \TYPO3\CMS\Reports\Status The deprecation log status.
191 */
192 protected function getDeprecationLogStatus()
193 {
194 $title = $this->getLanguageService()->getLL('status_configuration_DeprecationLog');
195 $value = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:disabled');
196 $message = '';
197 $severity = ReportStatus::OK;
198 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['enableDeprecationLog']) {
199 $value = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:enabled');
200 $message = '<p>' . $this->getLanguageService()->getLL('status_configuration_DeprecationLogEnabled') . '</p>';
201 $severity = ReportStatus::NOTICE;
202 $logFile = GeneralUtility::getDeprecationLogFileName();
203 $logFileSize = 0;
204 if (@file_exists($logFile)) {
205 $logFileSize = filesize($logFile);
206 $message .= '<p>' . sprintf($this->getLanguageService()->getLL('status_configuration_DeprecationLogFile'), '<code>' . $this->getDeprecationLogFileLink()) . '</code></p>';
207 $removeDeprecationLogFileUrl = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL') . '&amp;adminCmd=removeDeprecationLogFile';
208 $message .= '<p>' . sprintf($this->getLanguageService()->getLL('status_configuration_DeprecationLogSize'), GeneralUtility::formatSize($logFileSize)) . ' <a href="' . $removeDeprecationLogFileUrl . '">' . $this->getLanguageService()->getLL('status_configuration_DeprecationLogDeleteLink') . '</a></p>';
209 }
210 if ($logFileSize > $this->deprecationLogFileSizeWarningThreshold) {
211 $severity = ReportStatus::WARNING;
212 }
213 if ($logFileSize > $this->deprecationLogFileSizeErrorThreshold) {
214 $severity = ReportStatus::ERROR;
215 }
216 }
217 return GeneralUtility::makeInstance(ReportStatus::class, $title, $value, $message, $severity);
218 }
219
220 /**
221 * Warning, if fileCreateMask has write bit for 'others' set.
222 *
223 * @return \TYPO3\CMS\Reports\Status The writable status for 'others'
224 */
225 protected function getCreatedFilesWorldWritableStatus()
226 {
227 $value = $this->getLanguageService()->getLL('status_ok');
228 $message = '';
229 $severity = ReportStatus::OK;
230 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] % 10 & 2) {
231 $value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'];
232 $severity = ReportStatus::WARNING;
233 $message = $this->getLanguageService()->getLL('status_CreatedFilePermissions.writable');
234 }
235 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_CreatedFilePermissions'), $value, $message, $severity);
236 }
237
238 /**
239 * Warning, if folderCreateMask has write bit for 'others' set.
240 *
241 * @return \TYPO3\CMS\Reports\Status The writable status for 'others'
242 */
243 protected function getCreatedDirectoriesWorldWritableStatus()
244 {
245 $value = $this->getLanguageService()->getLL('status_ok');
246 $message = '';
247 $severity = ReportStatus::OK;
248 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] % 10 & 2) {
249 $value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'];
250 $severity = ReportStatus::WARNING;
251 $message = $this->getLanguageService()->getLL('status_CreatedDirectoryPermissions.writable');
252 }
253 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_CreatedDirectoryPermissions'), $value, $message, $severity);
254 }
255
256 /**
257 * Creates a link to the deprecation log file with the absolute path as the
258 * link text.
259 *
260 * @return string Link to the deprecation log file
261 */
262 protected function getDeprecationLogFileLink()
263 {
264 $logFile = GeneralUtility::getDeprecationLogFileName();
265 $linkToLogFile = PathUtility::getAbsoluteWebPath($logFile);
266 return '<a href="' . $linkToLogFile . '">' . $logFile . '</a>';
267 }
268
269 /**
270 * Checks if the default connection is a MySQL compatible database instance.
271 *
272 * @return bool
273 */
274 protected function isMysqlUsed()
275 {
276 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
277 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
278
279 return strpos($connection->getServerVersion(), 'MySQL') === 0;
280 }
281
282 /**
283 * Checks the character set of the default database and reports an error if it is not utf-8.
284 *
285 * @return ReportStatus
286 */
287 protected function getMysqlDatabaseUtf8Status()
288 {
289 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
290 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
291 /** @var QueryBuilder $queryBuilder */
292 $queryBuilder = $connection->createQueryBuilder();
293 $defaultDatabaseCharset = (string)$queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
294 ->from('information_schema.SCHEMATA')
295 ->where(
296 $queryBuilder->expr()->eq(
297 'SCHEMA_NAME',
298 $queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR)
299 )
300 )
301 ->setMaxResults(1)
302 ->execute()
303 ->fetchColumn();
304
305 $severity = ReportStatus::OK;
306 $statusValue = $this->getLanguageService()->getLL('status_ok');
307 // also allow utf8mb4
308 if (strpos($defaultDatabaseCharset, 'utf8') !== 0) {
309 // If the default character set is e.g. latin1, BUT all tables in the system are UTF-8,
310 // we assume that TYPO3 has the correct charset for adding tables, and everything is fine
311 $nonUtf8TableCollationsFound = $queryBuilder->select('table_collation')
312 ->from('information_schema.tables')
313 ->where(
314 $queryBuilder->expr()->andX(
315 $queryBuilder->expr()->eq('table_schema', $queryBuilder->quote($connection->getDatabase())),
316 $queryBuilder->expr()->notLike('table_collation', $queryBuilder->quote('utf8%'))
317 )
318 )
319 ->setMaxResults(1)
320 ->execute();
321
322 if ($nonUtf8TableCollationsFound->rowCount() > 0) {
323 $message = sprintf($this->getLanguageService()
324 ->getLL('status_MysqlDatabaseCharacterSet_Unsupported'), $defaultDatabaseCharset);
325 $severity = ReportStatus::ERROR;
326 $statusValue = $this->getLanguageService()->getLL('status_wrongValue');
327 } else {
328 $message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Info');
329 $severity = ReportStatus::INFO;
330 $statusValue = $this->getLanguageService()->getLL('status_info');
331 }
332 } else {
333 $message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Ok');
334 }
335
336 return GeneralUtility::makeInstance(
337 ReportStatus::class,
338 $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet'),
339 $statusValue,
340 $message,
341 $severity
342 );
343 }
344
345 /**
346 * Executes admin commands.
347 *
348 * Currently implemented commands are:
349 * - Remove deprecation log file
350 */
351 protected function executeAdminCommand()
352 {
353 $command = GeneralUtility::_GET('adminCmd');
354 switch ($command) {
355 case 'removeDeprecationLogFile':
356 self::removeDeprecationLogFile();
357 break;
358 default:
359 // intentionally left blank
360 }
361 }
362
363 /**
364 * Remove deprecation log file.
365 */
366 protected static function removeDeprecationLogFile()
367 {
368 if (@unlink(GeneralUtility::getDeprecationLogFileName())) {
369 $message = $GLOBALS['LANG']->getLL('status_configuration_DeprecationLogDeletedSuccessful');
370 $severity = FlashMessage::OK;
371 } else {
372 $message = $GLOBALS['LANG']->getLL('status_configuration_DeprecationLogDeletionFailed');
373 $severity = FlashMessage::ERROR;
374 }
375 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, '', $severity, true);
376 /** @var FlashMessageService $flashMessageService */
377 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
378 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
379 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
380 $defaultFlashMessageQueue->enqueue($flashMessage);
381 }
382
383 /**
384 * @return LanguageService
385 */
386 protected function getLanguageService()
387 {
388 return $GLOBALS['LANG'];
389 }
390 }