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