Passed
Push — master ( 970d9b...cc8532 )
by
unknown
14:07
created

AbstractExceptionHandler::writeLogEntries()   B

Complexity

Conditions 8
Paths 61

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 30
rs 8.4444
cc 8
nc 61
nop 2
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Error;
17
18
use Psr\Http\Message\ServerRequestInterface;
19
use Psr\Log\LoggerAwareInterface;
20
use Psr\Log\LoggerAwareTrait;
21
use TYPO3\CMS\Core\Database\ConnectionPool;
22
use TYPO3\CMS\Core\Http\ApplicationType;
23
use TYPO3\CMS\Core\SingletonInterface;
24
use TYPO3\CMS\Core\SysLog\Action as SystemLogGenericAction;
25
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
26
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
27
use TYPO3\CMS\Core\Utility\GeneralUtility;
28
use TYPO3\CMS\Core\Utility\HttpUtility;
29
30
/**
31
 * An abstract exception handler
32
 *
33
 * This file is a backport from TYPO3 Flow
34
 */
35
abstract class AbstractExceptionHandler implements ExceptionHandlerInterface, SingletonInterface, LoggerAwareInterface
36
{
37
    use LoggerAwareTrait;
38
39
    const CONTEXT_WEB = 'WEB';
40
    const CONTEXT_CLI = 'CLI';
41
42
    /**
43
     * Displays the given exception
44
     *
45
     * @param \Throwable $exception The throwable object.
46
     *
47
     * @throws \Exception
48
     */
49
    public function handleException(\Throwable $exception)
50
    {
51
        switch (PHP_SAPI) {
52
            case 'cli':
53
                $this->echoExceptionCLI($exception);
54
                break;
55
            default:
56
                $this->echoExceptionWeb($exception);
57
        }
58
    }
59
60
    /**
61
     * Writes exception to different logs
62
     *
63
     * @param \Throwable $exception The throwable object.
64
     * @param string $context The context where the exception was thrown, WEB or CLI
65
     */
66
    protected function writeLogEntries(\Throwable $exception, $context)
67
    {
68
        // Do not write any logs for this message to avoid filling up tables or files with illegal requests
69
        if ($exception->getCode() === 1396795884) {
70
            return;
71
        }
72
        $filePathAndName = $exception->getFile();
73
        $exceptionCodeNumber = $exception->getCode() > 0 ? '#' . $exception->getCode() . ': ' : '';
74
        $logTitle = 'Core: Exception handler (' . $context . ')';
75
        $logMessage = 'Uncaught TYPO3 Exception: ' . $exceptionCodeNumber . $exception->getMessage() . ' | '
76
            . get_class($exception) . ' thrown in file ' . $filePathAndName . ' in line ' . $exception->getLine();
77
        if ($context === 'WEB') {
78
            $logMessage .= '. Requested URL: ' . $this->anonymizeToken(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
79
        }
80
        // When database credentials are wrong, the exception is probably
81
        // caused by this. Therefor we cannot do any database operation,
82
        // otherwise this will lead into recurring exceptions.
83
        try {
84
            if ($this->logger) {
85
                // 'FE' if in FrontendApplication, else 'BE' (also in CLI without request object)
86
                $applicationType = ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
87
                    && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend() ? 'FE' : 'BE';
88
                $this->logger->critical($logTitle . ': ' . $logMessage, [
89
                    'TYPO3_MODE' => $applicationType,
90
                    'exception' => $exception
91
                ]);
92
            }
93
            // Write error message to sys_log table
94
            $this->writeLog($logTitle . ': ' . $logMessage);
95
        } catch (\Exception $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
96
        }
97
    }
98
99
    /**
100
     * Writes an exception in the sys_log table
101
     *
102
     * @param string $logMessage Default text that follows the message.
103
     */
104
    protected function writeLog($logMessage)
105
    {
106
        $connection = GeneralUtility::makeInstance(ConnectionPool::class)
107
            ->getConnectionForTable('sys_log');
108
109
        if (!$connection->isConnected()) {
110
            return;
111
        }
112
        $userId = 0;
113
        $workspace = 0;
114
        $data = [];
115
        $backendUser = $this->getBackendUser();
116
        if (is_object($backendUser)) {
117
            if (isset($backendUser->user['uid'])) {
118
                $userId = $backendUser->user['uid'];
119
            }
120
            if (isset($backendUser->workspace)) {
121
                $workspace = $backendUser->workspace;
122
            }
123
            if (!empty($backendUser->user['ses_backuserid'])) {
124
                $data['originalUser'] = $backendUser->user['ses_backuserid'];
125
            }
126
        }
127
128
        $connection->insert(
129
            'sys_log',
130
            [
131
                'userid' => $userId,
132
                'type' => SystemLogType::ERROR,
133
                'action' => SystemLogGenericAction::UNDEFINED,
134
                'error' => SystemLogErrorClassification::SYSTEM_ERROR,
135
                'details_nr' => 0,
136
                'details' => str_replace('%', '%%', $logMessage),
137
                'log_data' => empty($data) ? '' : serialize($data),
138
                'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
139
                'tstamp' => $GLOBALS['EXEC_TIME'],
140
                'workspace' => $workspace
141
            ]
142
        );
143
    }
144
145
    /**
146
     * Sends the HTTP Status 500 code, if $exception is *not* a
147
     * TYPO3\CMS\Core\Error\Http\StatusException and headers are not sent, yet.
148
     *
149
     * @param \Throwable $exception The throwable object.
150
     */
151
    protected function sendStatusHeaders(\Throwable $exception)
152
    {
153
        if (method_exists($exception, 'getStatusHeaders')) {
154
            $headers = $exception->getStatusHeaders();
155
        } else {
156
            $headers = [HttpUtility::HTTP_STATUS_500];
157
        }
158
        if (!headers_sent()) {
159
            foreach ($headers as $header) {
160
                header($header);
161
            }
162
        }
163
    }
164
165
    /**
166
     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
167
     */
168
    protected function getBackendUser()
169
    {
170
        return $GLOBALS['BE_USER'];
171
    }
172
173
    /**
174
     * Replaces the generated token with a generic equivalent
175
     *
176
     * @param string $requestedUrl
177
     * @return string
178
     */
179
    protected function anonymizeToken(string $requestedUrl): string
180
    {
181
        $pattern = '/(?<=[tT]oken=)[0-9a-fA-F]{40}/';
182
        return preg_replace($pattern, '--AnonymizedToken--', $requestedUrl);
183
    }
184
}
185