d931a0f8ff6bd832bedb9703b5f0a3a6421f6dec
[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\Core\Environment;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
21 use TYPO3\CMS\Core\Localization\LanguageService;
22 use TYPO3\CMS\Core\Registry;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Reports\Status as ReportStatus;
25 use TYPO3\CMS\Reports\StatusProviderInterface;
26
27 /**
28 * Performs some checks about the install tool protection status
29 */
30 class ConfigurationStatus implements StatusProviderInterface
31 {
32 /**
33 * Determines the Install Tool's status, mainly concerning its protection.
34 *
35 * @return array List of statuses
36 */
37 public function getStatus()
38 {
39 $statuses = [
40 'emptyReferenceIndex' => $this->getReferenceIndexStatus(),
41 ];
42 if ($this->isMemcachedUsed()) {
43 $statuses['memcachedConnection'] = $this->getMemcachedConnectionStatus();
44 }
45 if (!Environment::isWindows()) {
46 $statuses['createdFilesWorldWritable'] = $this->getCreatedFilesWorldWritableStatus();
47 $statuses['createdDirectoriesWorldWritable'] = $this->getCreatedDirectoriesWorldWritableStatus();
48 }
49 if ($this->isMysqlUsed()) {
50 $statuses['mysqlDatabaseUsesUtf8'] = $this->getMysqlDatabaseUtf8Status();
51 }
52 return $statuses;
53 }
54
55 /**
56 * Checks if sys_refindex is empty.
57 *
58 * @return \TYPO3\CMS\Reports\Status An object representing whether the reference index is empty or not
59 */
60 protected function getReferenceIndexStatus()
61 {
62 $value = $this->getLanguageService()->getLL('status_ok');
63 $message = '';
64 $severity = ReportStatus::OK;
65
66 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
67 $count = $queryBuilder
68 ->count('*')
69 ->from('sys_refindex')
70 ->execute()
71 ->fetchColumn(0);
72
73 $registry = GeneralUtility::makeInstance(Registry::class);
74 $lastRefIndexUpdate = $registry->get('core', 'sys_refindex_lastUpdate');
75 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
76 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
77 if (!$count && $lastRefIndexUpdate) {
78 $value = $this->getLanguageService()->getLL('status_empty');
79 $severity = ReportStatus::WARNING;
80 $url = (string)$uriBuilder->buildUriFromRoute('system_dbint') . '&id=0&SET[function]=refindex';
81 $message = sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.backend_reference_index'), '<a href="' . htmlspecialchars($url) . '">', '</a>', BackendUtility::datetime($lastRefIndexUpdate));
82 }
83 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_referenceIndex'), $value, $message, $severity);
84 }
85
86 /**
87 * Checks whether memcached is configured, if that's the case we assume it's also used.
88 *
89 * @return bool TRUE if memcached is used, FALSE otherwise.
90 */
91 protected function isMemcachedUsed()
92 {
93 $memcachedUsed = false;
94 $memcachedServers = $this->getConfiguredMemcachedServers();
95 if (!empty($memcachedServers)) {
96 $memcachedUsed = true;
97 }
98 return $memcachedUsed;
99 }
100
101 /**
102 * Gets the configured memcached server connections.
103 *
104 * @return array An array of configured memcached server connections.
105 */
106 protected function getConfiguredMemcachedServers()
107 {
108 $configurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] ?? [];
109 $memcachedServers = [];
110 foreach ($configurations as $table => $conf) {
111 if (is_array($conf)) {
112 foreach ($conf as $key => $value) {
113 if ($value === \TYPO3\CMS\Core\Cache\Backend\MemcachedBackend::class) {
114 $memcachedServers = $configurations[$table]['options']['servers'];
115 break;
116 }
117 }
118 }
119 }
120 return $memcachedServers;
121 }
122
123 /**
124 * Checks whether TYPO3 can connect to the configured memcached servers.
125 *
126 * @return \TYPO3\CMS\Reports\Status An object representing whether TYPO3 can connect to the configured memcached servers
127 */
128 protected function getMemcachedConnectionStatus()
129 {
130 $value = $this->getLanguageService()->getLL('status_ok');
131 $message = '';
132 $severity = ReportStatus::OK;
133 $failedConnections = [];
134 $defaultMemcachedPort = ini_get('memcache.default_port');
135 $memcachedServers = $this->getConfiguredMemcachedServers();
136 if (function_exists('memcache_connect') && is_array($memcachedServers)) {
137 foreach ($memcachedServers as $testServer) {
138 $configuredServer = $testServer;
139 if (strpos($testServer, 'unix://') === 0) {
140 $host = $testServer;
141 $port = 0;
142 } else {
143 if (strpos($testServer, 'tcp://') === 0) {
144 $testServer = substr($testServer, 6);
145 }
146 if (strstr($testServer, ':') !== false) {
147 list($host, $port) = explode(':', $testServer, 2);
148 } else {
149 $host = $testServer;
150 $port = $defaultMemcachedPort;
151 }
152 }
153 $memcachedConnection = @memcache_connect($host, $port);
154 if ($memcachedConnection != null) {
155 memcache_close($memcachedConnection);
156 } else {
157 $failedConnections[] = $configuredServer;
158 }
159 }
160 }
161 if (!empty($failedConnections)) {
162 $value = $this->getLanguageService()->getLL('status_connectionFailed');
163 $severity = ReportStatus::WARNING;
164 $message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.memcache_not_usable') . '<br /><br />' . '<ul><li>' . implode('</li><li>', $failedConnections) . '</li></ul>';
165 }
166 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_memcachedConfiguration'), $value, $message, $severity);
167 }
168
169 /**
170 * Warning, if fileCreateMask has write bit for 'others' set.
171 *
172 * @return \TYPO3\CMS\Reports\Status The writable status for 'others'
173 */
174 protected function getCreatedFilesWorldWritableStatus()
175 {
176 $value = $this->getLanguageService()->getLL('status_ok');
177 $message = '';
178 $severity = ReportStatus::OK;
179 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] % 10 & 2) {
180 $value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'];
181 $severity = ReportStatus::WARNING;
182 $message = $this->getLanguageService()->getLL('status_CreatedFilePermissions.writable');
183 }
184 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_CreatedFilePermissions'), $value, $message, $severity);
185 }
186
187 /**
188 * Warning, if folderCreateMask has write bit for 'others' set.
189 *
190 * @return \TYPO3\CMS\Reports\Status The writable status for 'others'
191 */
192 protected function getCreatedDirectoriesWorldWritableStatus()
193 {
194 $value = $this->getLanguageService()->getLL('status_ok');
195 $message = '';
196 $severity = ReportStatus::OK;
197 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] % 10 & 2) {
198 $value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'];
199 $severity = ReportStatus::WARNING;
200 $message = $this->getLanguageService()->getLL('status_CreatedDirectoryPermissions.writable');
201 }
202 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_CreatedDirectoryPermissions'), $value, $message, $severity);
203 }
204
205 /**
206 * Checks if the default connection is a MySQL compatible database instance.
207 *
208 * @return bool
209 */
210 protected function isMysqlUsed()
211 {
212 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
213 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
214
215 return strpos($connection->getServerVersion(), 'MySQL') === 0;
216 }
217
218 /**
219 * Checks the character set of the default database and reports an error if it is not utf-8.
220 *
221 * @return ReportStatus
222 */
223 protected function getMysqlDatabaseUtf8Status()
224 {
225 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
226 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
227 /** @var QueryBuilder $queryBuilder */
228 $queryBuilder = $connection->createQueryBuilder();
229 $defaultDatabaseCharset = (string)$queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
230 ->from('information_schema.SCHEMATA')
231 ->where(
232 $queryBuilder->expr()->eq(
233 'SCHEMA_NAME',
234 $queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR)
235 )
236 )
237 ->setMaxResults(1)
238 ->execute()
239 ->fetchColumn();
240
241 $severity = ReportStatus::OK;
242 $statusValue = $this->getLanguageService()->getLL('status_ok');
243 // also allow utf8mb4
244 if (strpos($defaultDatabaseCharset, 'utf8') !== 0) {
245 // If the default character set is e.g. latin1, BUT all tables in the system are UTF-8,
246 // we assume that TYPO3 has the correct charset for adding tables, and everything is fine
247 $nonUtf8TableCollationsFound = $queryBuilder->select('table_collation')
248 ->from('information_schema.tables')
249 ->where(
250 $queryBuilder->expr()->andX(
251 $queryBuilder->expr()->eq('table_schema', $queryBuilder->quote($connection->getDatabase())),
252 $queryBuilder->expr()->notLike('table_collation', $queryBuilder->quote('utf8%'))
253 )
254 )
255 ->setMaxResults(1)
256 ->execute();
257
258 if ($nonUtf8TableCollationsFound->rowCount() > 0) {
259 $message = sprintf($this->getLanguageService()
260 ->getLL('status_MysqlDatabaseCharacterSet_Unsupported'), $defaultDatabaseCharset);
261 $severity = ReportStatus::ERROR;
262 $statusValue = $this->getLanguageService()->getLL('status_wrongValue');
263 } else {
264 $message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Info');
265 $severity = ReportStatus::INFO;
266 $statusValue = $this->getLanguageService()->getLL('status_info');
267 }
268 } else {
269 $message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Ok');
270 }
271
272 return GeneralUtility::makeInstance(
273 ReportStatus::class,
274 $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet'),
275 $statusValue,
276 $message,
277 $severity
278 );
279 }
280
281 /**
282 * @return LanguageService
283 */
284 protected function getLanguageService()
285 {
286 return $GLOBALS['LANG'];
287 }
288 }