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