3800943d0802edb0688efff44b1534f46e7c4ce7
[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\DatabaseConnection;
20 use TYPO3\CMS\Core\Messaging\FlashMessage;
21 use TYPO3\CMS\Core\Messaging\FlashMessageService;
22 use TYPO3\CMS\Core\Registry;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Core\Utility\PathUtility;
25 use TYPO3\CMS\Lang\LanguageService;
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 = array(
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/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 = array();
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 = array();
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/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/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/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 * Verifies that MySQL is used.
271 *
272 * @return bool
273 */
274 protected function isMysqlUsed()
275 {
276 return get_class($this->getDatabaseConnection()) == DatabaseConnection::class;
277 }
278
279 /**
280 * Checks the character set of the database and reports an error if it is not utf-8.
281 *
282 * @return ReportStatus
283 */
284 protected function getMysqlDatabaseUtf8Status()
285 {
286 $result = $this->getDatabaseConnection()->admin_query('SHOW VARIABLES LIKE "character_set_database"');
287 $row = $this->getDatabaseConnection()->sql_fetch_assoc($result);
288
289 $key = $row['Variable_name'];
290 $value = $row['Value'];
291
292 $message = '';
293 $severity = ReportStatus::OK;
294 $statusValue = $this->getLanguageService()->getLL('status_ok');
295
296 if ($key !== 'character_set_database') {
297 $message = sprintf($this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_CheckFailed'), $key);
298 $severity = ReportStatus::WARNING;
299 $statusValue = $this->getLanguageService()->getLL('status_checkFailed');
300 }
301 // also allow utf8mb4
302 if (substr($value, 0, 4) !== 'utf8') {
303 $message = sprintf($this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Unsupported'), $value);
304 $severity = ReportStatus::ERROR;
305 $statusValue = $this->getLanguageService()->getLL('status_wrongValue');
306 } else {
307 $message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Ok');
308 }
309
310 return GeneralUtility::makeInstance(ReportStatus::class,
311 $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet'),
312 $statusValue, $message, $severity
313 );
314 }
315
316 /**
317 * Executes admin commands.
318 *
319 * Currently implemented commands are:
320 * - Remove deprecation log file
321 *
322 * @return void
323 */
324 protected function executeAdminCommand()
325 {
326 $command = GeneralUtility::_GET('adminCmd');
327 switch ($command) {
328 case 'removeDeprecationLogFile':
329 self::removeDeprecationLogFile();
330 break;
331 default:
332 // intentionally left blank
333 }
334 }
335
336 /**
337 * Remove deprecation log file.
338 *
339 * @return void
340 */
341 protected static function removeDeprecationLogFile()
342 {
343 if (@unlink(GeneralUtility::getDeprecationLogFileName())) {
344 $message = $GLOBALS['LANG']->getLL('status_configuration_DeprecationLogDeletedSuccessful');
345 $severity = FlashMessage::OK;
346 } else {
347 $message = $GLOBALS['LANG']->getLL('status_configuration_DeprecationLogDeletionFailed');
348 $severity = FlashMessage::ERROR;
349 }
350 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, '', $severity, true);
351 /** @var FlashMessageService $flashMessageService */
352 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
353 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
354 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
355 $defaultFlashMessageQueue->enqueue($flashMessage);
356 }
357
358 /**
359 * @return LanguageService
360 */
361 protected function getLanguageService()
362 {
363 return $GLOBALS['LANG'];
364 }
365
366 /**
367 * @return DatabaseConnection
368 */
369 protected function getDatabaseConnection()
370 {
371 return $GLOBALS['TYPO3_DB'];
372 }
373 }