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