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