[TASK] Use a dedicated logger for E_USER_DEPRECATION
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Error / ErrorHandler.php
1 <?php
2 namespace TYPO3\CMS\Core\Error;
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 Psr\Log\LoggerAwareInterface;
18 use Psr\Log\LoggerAwareTrait;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Log\LogLevel;
21 use TYPO3\CMS\Core\Log\LogManager;
22 use TYPO3\CMS\Core\Messaging\FlashMessage;
23 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26 /**
27 * Global error handler for TYPO3
28 *
29 * This file is a backport from TYPO3 Flow
30 */
31 class ErrorHandler implements ErrorHandlerInterface, LoggerAwareInterface
32 {
33 use LoggerAwareTrait;
34
35 /**
36 * Error levels which should result in an exception thrown.
37 *
38 * @var int
39 */
40 protected $exceptionalErrors = 0;
41
42 /**
43 * Whether to write a flash message in case of an error
44 *
45 * @var bool
46 */
47 protected $debugMode = false;
48
49 /**
50 * Registers this class as default error handler
51 *
52 * @param int $errorHandlerErrors The integer representing the E_* error level which should be
53 */
54 public function __construct($errorHandlerErrors)
55 {
56 $excludedErrors = E_COMPILE_WARNING | E_COMPILE_ERROR | E_CORE_WARNING | E_CORE_ERROR | E_PARSE | E_ERROR;
57 // reduces error types to those a custom error handler can process
58 $errorHandlerErrors = $errorHandlerErrors & ~$excludedErrors;
59 set_error_handler([$this, 'handleError'], $errorHandlerErrors);
60 }
61
62 /**
63 * Defines which error levels should result in an exception thrown.
64 *
65 * @param int $exceptionalErrors The integer representing the E_* error level to handle as exceptions
66 */
67 public function setExceptionalErrors($exceptionalErrors)
68 {
69 $exceptionalErrors = (int)$exceptionalErrors;
70 // We always disallow E_USER_DEPRECATED to generate exceptions as this may cause
71 // bad user experience specifically during upgrades.
72 $this->exceptionalErrors = $exceptionalErrors & ~E_USER_DEPRECATED;
73 }
74
75 /**
76 * @param bool $debugMode
77 */
78 public function setDebugMode($debugMode)
79 {
80 $this->debugMode = (bool)$debugMode;
81 }
82
83 /**
84 * Handles an error.
85 * If the error is registered as exceptionalError it will by converted into an exception, to be handled
86 * by the configured exceptionhandler. Additionally the error message is written to the configured logs.
87 * If TYPO3_MODE is 'BE' the error message is also added to the flashMessageQueue, in FE the error message
88 * is displayed in the admin panel (as TsLog message)
89 *
90 * @param int $errorLevel The error level - one of the E_* constants
91 * @param string $errorMessage The error message
92 * @param string $errorFile Name of the file the error occurred in
93 * @param int $errorLine Line number where the error occurred
94 * @return bool
95 * @throws Exception with the data passed to this method if the error is registered as exceptionalError
96 */
97 public function handleError($errorLevel, $errorMessage, $errorFile, $errorLine)
98 {
99 // Don't do anything if error_reporting is disabled by an @ sign
100 if (error_reporting() === 0) {
101 return true;
102 }
103 $errorLevels = [
104 E_WARNING => 'PHP Warning',
105 E_NOTICE => 'PHP Notice',
106 E_USER_ERROR => 'PHP User Error',
107 E_USER_WARNING => 'PHP User Warning',
108 E_USER_NOTICE => 'PHP User Notice',
109 E_STRICT => 'PHP Runtime Notice',
110 E_RECOVERABLE_ERROR => 'PHP Catchable Fatal Error',
111 E_USER_DEPRECATED => 'TYPO3 Deprecation Notice',
112 E_DEPRECATED => 'PHP Runtime Deprecation Notice'
113 ];
114 $message = $errorLevels[$errorLevel] . ': ' . $errorMessage . ' in ' . $errorFile . ' line ' . $errorLine;
115 if ($errorLevel & $this->exceptionalErrors) {
116 throw new Exception($message, 1476107295);
117 }
118 switch ($errorLevel) {
119 case E_USER_ERROR:
120 case E_RECOVERABLE_ERROR:
121 // no $flashMessageSeverity, as there will be no flash message for errors
122 $severity = 2;
123 break;
124 case E_USER_WARNING:
125 case E_WARNING:
126 $flashMessageSeverity = FlashMessage::WARNING;
127 $severity = 1;
128 break;
129 default:
130 $flashMessageSeverity = FlashMessage::NOTICE;
131 $severity = 0;
132 }
133 $logTitle = 'Core: Error handler (' . TYPO3_MODE . ')';
134 $message = $logTitle . ': ' . $message;
135
136 if ($errorLevel === E_USER_DEPRECATED) {
137 $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger('TYPO3.CMS.deprecations');
138 $logger->notice($message);
139 return true;
140 }
141 if ($this->logger) {
142 $this->logger->log(LogLevel::NOTICE - $severity, $message);
143 }
144
145 // Write error message to TSlog (admin panel)
146 $timeTracker = $this->getTimeTracker();
147 if (is_object($timeTracker)) {
148 $timeTracker->setTSlogMessage($message, $severity + 1);
149 }
150 // Write error message to sys_log table (ext: belog, Tools->Log)
151 if ($errorLevel & $GLOBALS['TYPO3_CONF_VARS']['SYS']['belogErrorReporting']) {
152 // Silently catch in case an error occurs before a database connection exists.
153 try {
154 $this->writeLog($message, $severity);
155 } catch (\Exception $e) {
156 }
157 }
158 if ($severity === 2) {
159 // Let the internal handler continue. This will stop the script
160 return false;
161 }
162 if ($this->debugMode) {
163 /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */
164 $flashMessage = GeneralUtility::makeInstance(
165 \TYPO3\CMS\Core\Messaging\FlashMessage::class,
166 $message,
167 $errorLevels[$errorLevel],
168 $flashMessageSeverity
169 );
170 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
171 $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
172 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
173 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
174 $defaultFlashMessageQueue->enqueue($flashMessage);
175 }
176 // Don't execute PHP internal error handler
177 return true;
178 }
179
180 /**
181 * Writes an error in the sys_log table
182 *
183 * @param string $logMessage Default text that follows the message (in english!).
184 * @param int $severity The error level of the message (0 = OK, 1 = warning, 2 = error)
185 */
186 protected function writeLog($logMessage, $severity)
187 {
188 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
189 ->getConnectionForTable('sys_log');
190 if ($connection->isConnected()) {
191 $userId = 0;
192 $workspace = 0;
193 $data = [];
194 $backendUser = $this->getBackendUser();
195 if (is_object($backendUser)) {
196 if (isset($backendUser->user['uid'])) {
197 $userId = $backendUser->user['uid'];
198 }
199 if (isset($backendUser->workspace)) {
200 $workspace = $backendUser->workspace;
201 }
202 if (!empty($backendUser->user['ses_backuserid'])) {
203 $data['originalUser'] = $backendUser->user['ses_backuserid'];
204 }
205 }
206
207 $connection->insert(
208 'sys_log',
209 [
210 'userid' => $userId,
211 'type' => 5,
212 'action' => 0,
213 'error' => $severity,
214 'details_nr' => 0,
215 'details' => str_replace('%', '%%', $logMessage),
216 'log_data' => empty($data) ? '' : serialize($data),
217 'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
218 'tstamp' => $GLOBALS['EXEC_TIME'],
219 'workspace' => $workspace
220 ]
221 );
222 }
223 }
224
225 /**
226 * @return TimeTracker
227 */
228 protected function getTimeTracker()
229 {
230 return GeneralUtility::makeInstance(TimeTracker::class);
231 }
232
233 /**
234 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
235 */
236 protected function getBackendUser()
237 {
238 return $GLOBALS['BE_USER'];
239 }
240 }