[!!!][TASK] Use native trigger_error and ErrorHandler for deprecations
[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 $memcachedServers = [];
106 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'])) {
107 foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] as $table => $conf) {
108 if (is_array($conf)) {
109 foreach ($conf as $key => $value) {
110 if (!is_array($value) && $value === \TYPO3\CMS\Core\Cache\Backend\MemcachedBackend::class) {
111 $memcachedServers = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$table]['options']['servers'];
112 break;
113 }
114 }
115 }
116 }
117 }
118 return $memcachedServers;
119 }
120
121 /**
122 * Checks whether TYPO3 can connect to the configured memcached servers.
123 *
124 * @return \TYPO3\CMS\Reports\Status An object representing whether TYPO3 can connect to the configured memcached servers
125 */
126 protected function getMemcachedConnectionStatus()
127 {
128 $value = $this->getLanguageService()->getLL('status_ok');
129 $message = '';
130 $severity = ReportStatus::OK;
131 $failedConnections = [];
132 $defaultMemcachedPort = ini_get('memcache.default_port');
133 $memcachedServers = $this->getConfiguredMemcachedServers();
134 if (function_exists('memcache_connect') && is_array($memcachedServers)) {
135 foreach ($memcachedServers as $testServer) {
136 $configuredServer = $testServer;
137 if (substr($testServer, 0, 7) === 'unix://') {
138 $host = $testServer;
139 $port = 0;
140 } else {
141 if (substr($testServer, 0, 6) === 'tcp://') {
142 $testServer = substr($testServer, 6);
143 }
144 if (strstr($testServer, ':') !== false) {
145 list($host, $port) = explode(':', $testServer, 2);
146 } else {
147 $host = $testServer;
148 $port = $defaultMemcachedPort;
149 }
150 }
151 $memcachedConnection = @memcache_connect($host, $port);
152 if ($memcachedConnection != null) {
153 memcache_close($memcachedConnection);
154 } else {
155 $failedConnections[] = $configuredServer;
156 }
157 }
158 }
159 if (!empty($failedConnections)) {
160 $value = $this->getLanguageService()->getLL('status_connectionFailed');
161 $severity = ReportStatus::WARNING;
162 $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>';
163 }
164 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_memcachedConfiguration'), $value, $message, $severity);
165 }
166
167 /**
168 * Warning, if fileCreateMask has write bit for 'others' set.
169 *
170 * @return \TYPO3\CMS\Reports\Status The writable status for 'others'
171 */
172 protected function getCreatedFilesWorldWritableStatus()
173 {
174 $value = $this->getLanguageService()->getLL('status_ok');
175 $message = '';
176 $severity = ReportStatus::OK;
177 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] % 10 & 2) {
178 $value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'];
179 $severity = ReportStatus::WARNING;
180 $message = $this->getLanguageService()->getLL('status_CreatedFilePermissions.writable');
181 }
182 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_CreatedFilePermissions'), $value, $message, $severity);
183 }
184
185 /**
186 * Warning, if folderCreateMask has write bit for 'others' set.
187 *
188 * @return \TYPO3\CMS\Reports\Status The writable status for 'others'
189 */
190 protected function getCreatedDirectoriesWorldWritableStatus()
191 {
192 $value = $this->getLanguageService()->getLL('status_ok');
193 $message = '';
194 $severity = ReportStatus::OK;
195 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] % 10 & 2) {
196 $value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'];
197 $severity = ReportStatus::WARNING;
198 $message = $this->getLanguageService()->getLL('status_CreatedDirectoryPermissions.writable');
199 }
200 return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_CreatedDirectoryPermissions'), $value, $message, $severity);
201 }
202
203 /**
204 * Checks if the default connection is a MySQL compatible database instance.
205 *
206 * @return bool
207 */
208 protected function isMysqlUsed()
209 {
210 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
211 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
212
213 return strpos($connection->getServerVersion(), 'MySQL') === 0;
214 }
215
216 /**
217 * Checks the character set of the default database and reports an error if it is not utf-8.
218 *
219 * @return ReportStatus
220 */
221 protected function getMysqlDatabaseUtf8Status()
222 {
223 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
224 ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
225 /** @var QueryBuilder $queryBuilder */
226 $queryBuilder = $connection->createQueryBuilder();
227 $defaultDatabaseCharset = (string)$queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
228 ->from('information_schema.SCHEMATA')
229 ->where(
230 $queryBuilder->expr()->eq(
231 'SCHEMA_NAME',
232 $queryBuilder->createNamedParameter($connection->getDatabase(), \PDO::PARAM_STR)
233 )
234 )
235 ->setMaxResults(1)
236 ->execute()
237 ->fetchColumn();
238
239 $severity = ReportStatus::OK;
240 $statusValue = $this->getLanguageService()->getLL('status_ok');
241 // also allow utf8mb4
242 if (strpos($defaultDatabaseCharset, 'utf8') !== 0) {
243 // If the default character set is e.g. latin1, BUT all tables in the system are UTF-8,
244 // we assume that TYPO3 has the correct charset for adding tables, and everything is fine
245 $nonUtf8TableCollationsFound = $queryBuilder->select('table_collation')
246 ->from('information_schema.tables')
247 ->where(
248 $queryBuilder->expr()->andX(
249 $queryBuilder->expr()->eq('table_schema', $queryBuilder->quote($connection->getDatabase())),
250 $queryBuilder->expr()->notLike('table_collation', $queryBuilder->quote('utf8%'))
251 )
252 )
253 ->setMaxResults(1)
254 ->execute();
255
256 if ($nonUtf8TableCollationsFound->rowCount() > 0) {
257 $message = sprintf($this->getLanguageService()
258 ->getLL('status_MysqlDatabaseCharacterSet_Unsupported'), $defaultDatabaseCharset);
259 $severity = ReportStatus::ERROR;
260 $statusValue = $this->getLanguageService()->getLL('status_wrongValue');
261 } else {
262 $message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Info');
263 $severity = ReportStatus::INFO;
264 $statusValue = $this->getLanguageService()->getLL('status_info');
265 }
266 } else {
267 $message = $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet_Ok');
268 }
269
270 return GeneralUtility::makeInstance(
271 ReportStatus::class,
272 $this->getLanguageService()->getLL('status_MysqlDatabaseCharacterSet'),
273 $statusValue,
274 $message,
275 $severity
276 );
277 }
278
279 /**
280 * @return LanguageService
281 */
282 protected function getLanguageService()
283 {
284 return $GLOBALS['LANG'];
285 }
286 }