Report::typeToLevel()   C
last analyzed

Complexity

Conditions 14
Paths 14

Size

Total Lines 26
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 210

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 20
c 1
b 0
f 0
nc 14
nop 1
dl 0
loc 26
ccs 0
cts 20
cp 0
crap 210
rs 6.2666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace App;
4
5
use App\Model\Table\IncidentsTable;
6
use Cake\Utility\Security;
7
use DateTime;
8
use stdClass;
9
10
use function array_filter;
11
use function array_keys;
12
use function array_merge;
13
use function bin2hex;
14
use function crc32;
15
use function date;
16
use function explode;
17
use function html_entity_decode;
18
use function htmlspecialchars_decode;
19
use function is_string;
20
use function json_decode;
21
use function openssl_random_pseudo_bytes;
22
use function parse_str;
23
use function parse_url;
24
use function strpos;
25
26
use const ENT_HTML5;
27
use const ENT_QUOTES;
28
use const PHP_URL_QUERY;
29
30
/**
31
 * Represents an user report
32
 */
33
class Report extends stdClass
34
{
35
    /** @var string */
36
    private $internal____date = null;
37
38
    /** @var string */
39
    private $internal____eventId = null;
40
41
    /** @var string */
42
    private $internal____userMessage = null;
43
44 14
    public function hasUserFeedback(): bool
45
    {
46 14
        return $this->internal____userMessage !== null;
47
    }
48
49 7
    public function getUserFeedback(): string
50
    {
51 7
        return $this->internal____userMessage ?? '';
52
    }
53
54 28
    public static function fromString(string $input): Report
55
    {
56 28
        $obj = json_decode($input);
57
58 28
        return self::fromObject((object) $obj);
59
    }
60
61 28
    public static function fromObject(stdClass $input): Report
62
    {
63 28
        $obj = (object) $input;
64 28
        $keys = array_keys((array) $obj);
65 28
        $report = new Report();
66 28
        foreach ($keys as $propertyName) {
67 28
            if ($propertyName === 'steps') {
68 14
                $report->internal____userMessage = $obj->{$propertyName};
69
            } else {
70 28
                $report->{$propertyName} = $obj->{$propertyName};
71
            }
72
        }
73
74 28
        return $report;
75
    }
76
77
    public function setTimestamp(string $timestamp): void
78
    {
79
        $this->internal____date = $timestamp;
80
    }
81
82 14
    public function getEventId(): string
83
    {
84 14
        if ($this->internal____eventId === null) {
85 14
            $this->internal____eventId = bin2hex((string) openssl_random_pseudo_bytes(16));
86
        }
87
88 14
        return $this->internal____eventId;
89
    }
90
91 14
    public function getTimestampUTC(): string
92
    {
93 14
        return $this->internal____date ?? date(DateTime::RFC3339);
94
    }
95
96 21
    public function getTags(): stdClass
97
    {
98
        /*
99
            "pma_version": "4.8.6-dev",
100
            "browser_name": "CHROME",
101
            "browser_version": "72.0.3626.122",
102
            "user_os": "Linux",
103
            "server_software": "nginx/1.14.0",
104
            "user_agent_string": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.122 Safari/537.36 Vivaldi/2.3.1440.61",
105
            "locale": "fr",
106
            "configuration_storage": "enabled",
107
            "php_version": "7.2.16-1+ubuntu18.04.1+deb.sury.org+1",
108
            "exception_type": "php",
109
        */
110 21
        $release = $this->getPmaVersion();
111 21
        $version = explode('.', $release, 3);
112 21
        $tags = new stdClass();
113 21
        $tags->version_major = $version[0];
114 21
        $tags->version_series = $version[0] . '.' . $version[1];
115
        //$tags->pma_version = $this->{'pma_version'} ?? null;
116
        //$tags->browser_name = $this->{'browser_name'} ?? null;
117
        //$tags->browser_version = $this->{'browser_version'} ?? null;
118
        //$tags->user_os = $this->{'user_os'} ?? null;
119 21
        $tags->server_software = $this->{'server_software'} ?? null;
120 21
        $tags->user_agent_string = $this->{'user_agent_string'} ?? null;
121 21
        $tags->locale = $this->{'locale'} ?? null;
122 21
        $tags->configuration_storage = ($this->{'configuration_storage'} ?? '') === 'enabled'; // "enabled" or "disabled"
123 21
        $tags->php_version = $this->{'php_version'} ?? null;
124 21
        $tags->exception_type = $this->{'exception_type'} ?? null;// js or php
125
126 21
        return $tags;
127
    }
128
129 14
    public function getContexts(): stdClass
130
    {
131 14
        $contexts = new stdClass();
132 14
        $contexts->os = new stdClass();
133 14
        $contexts->os->name = $this->{'user_os'} ?? null;
134
135 14
        $contexts->browser = new stdClass();
136 14
        $contexts->browser->name = $this->{'browser_name'} ?? null;
137 14
        $contexts->browser->version = $this->{'browser_version'} ?? null;
138
139 14
        return $contexts;
140
    }
141
142 14
    public function decode(string $text): string
143
    {
144 14
        return htmlspecialchars_decode(html_entity_decode($text, ENT_QUOTES | ENT_HTML5));
145
    }
146
147
    /**
148
     * @return array<string,mixed>
149
     */
150 14
    public function getExceptionJS(): array
151
    {
152 14
        $exception = new stdClass();
153 14
        $exception->type = $this->decode($this->{'exception'}->name ?? '');
154 14
        $exception->value = $this->decode($this->{'exception'}->message ?? '');
155 14
        $exception->stacktrace = new stdClass();
156 14
        $exception->stacktrace->frames = [];
157 14
        $exStack = ($this->{'exception'} ?? (object) ['stack' => []])->{'stack'} ?? [];
158 14
        foreach ($exStack as $stack) {
159 14
            $exception->stacktrace->frames[] = [
160 14
                'platform' => 'javascript',
161 14
                'function' => $this->decode($stack->{'func'} ?? ''),
162 14
                'lineno' => (int) ($stack->{'line'} ?? 0),
163 14
                'colno' => (int) ($stack->{'column'} ?? 0),
164 14
                'abs_path' => $stack->{'uri'} ?? '',
165 14
                'filename' => $stack->{'scriptname'} ?? '',
166
            ];
167
        }
168
169
        return [
170 14
            'platform' => 'javascript',
171
            'exception' => [
172 14
                'values' => [$exception],
173
            ],
174 14
            'message' => $this->decode($this->{'exception'}->message ?? ''),
175 14
            'culprit' => $this->{'script_name'} ?? $this->{'uri'} ?? null,
176
        ];
177
    }
178
179 14
    public function getExtras(): stdClass
180
    {
181 14
        return new stdClass();
182
    }
183
184 14
    public function getUserMessage(): stdClass
185
    {
186 14
        $userMessage = new stdClass();
187 14
        $userMessage->{'message'} = $this->decode($this->{'description'} ?? '');
188
189 14
        return $userMessage;
190
    }
191
192 21
    public function getUserId(): string
193
    {
194
        // Do not use the Ip as real data, protect the user !
195 21
        $userIp = $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '';
196 21
        $serverSoftware = $this->{'server_software'} ?? null;
197 21
        $userAgentString = $this->{'user_agent_string'} ?? null;
198 21
        $locale = $this->{'locale'} ?? null;
199 21
        $configurationStorage = $this->{'configuration_storage'} ?? null;
200 21
        $phpVersion = $this->{'php_version'} ?? null;
201
202 21
        $userIp = Security::hash(
203 21
            $userIp . crc32($userIp),
204 21
            'sha256',
205 21
            true // Enable app security salt
206
        );// Make finding back the Ip near to impossible
207
208 21
        $user = new stdClass();
0 ignored issues
show
Unused Code introduced by
The assignment to $user is dead and can be removed.
Loading history...
209
210
        // A user can be anonymously identified using the hash of the hashed IP + server software
211
        // + the UA + the locale + is configuration storage enabled + php version
212
        // Reversing the process would be near to impossible and anyway all the found data would be
213
        // already known and public data
214 21
        return Security::hash(
215 21
            $userIp . $serverSoftware . $userAgentString . $locale . $configurationStorage . $phpVersion,
216 21
            'sha256',
217 21
            true // Enable app security salt
218
        );
219
    }
220
221 21
    public function getUser(): stdClass
222
    {
223 21
        $user = new stdClass();
224 21
        $user->id = $this->getUserId();
225 21
        $user->ip_address = '0.0.0.0';
226
227 21
        return $user;
228
    }
229
230 14
    private function findRoute(?string $uri): ?string
231
    {
232 14
        if ($uri === null) {
233
            return null;
234
        }
235
236 14
        $query = parse_url($uri, PHP_URL_QUERY);// foo=bar&a=b
237 14
        if (! is_string($query)) {
0 ignored issues
show
introduced by
The condition is_string($query) is always true.
Loading history...
238
            return null;
239
        }
240
241 14
        $output = [];
242 14
        parse_str($query, $output);
243
244 14
        return $output['route'] ?? null;
245
    }
246
247 14
    public function getRoute(): ?string
248
    {
249 14
        if (isset($this->{'exception'})) {
250 14
            return $this->findRoute($this->{'exception'}->{'uri'} ?? null);
251
        }
252
253
        return null;
254
    }
255
256 14
    public function isMultiReports(): bool
257
    {
258 14
        return isset($this->{'exception_type'}) && $this->{'exception_type'} === 'php';
259
    }
260
261
    public function typeToLevel(string $type): string
262
    {
263
        switch ($type) {
264
            case 'Internal error':
265
            case 'Parsing Error':
266
            case 'Error':
267
            case 'Core Error':
268
                return 'error';
269
270
            case 'User Error':
271
            case 'User Warning':
272
            case 'User Notice':
273
                return 'info';
274
275
            case 'Warning':
276
            case 'Runtime Notice':
277
            case 'Deprecation Notice':
278
            case 'Notice':
279
            case 'Compile Warning':
280
                return 'warning';
281
282
            case 'Catchable Fatal Error':
283
                return 'tatal';
284
285
            default:
286
                return 'error';
287
        }
288
    }
289
290
    /**
291
     * @return array<int,array<string,mixed>>
292
     */
293
    public function getMultiDataToSend(): array
294
    {
295
        $reports = [];
296
        /*
297
        {
298
            "lineNum": 272,
299
            "file": "./libraries/classes/Plugins/Export/ExportXml.php",
300
            "type": "Warning",
301
            "msg": "count(): Parameter must be an array or an object that implements Countable",
302
            "stackTrace": [
303
                {
304
                "file": "./libraries/classes/Plugins/Export/ExportXml.php",
305
                "line": 272,
306
                "function": "count",
307
                "args": [
308
                    "NULL"
309
                ]
310
                },
311
                {
312
                "file": "./export.php",
313
                "line": 415,
314
                "function": "exportHeader",
315
                "class": "PhpMyAdmin\\Plugins\\Export\\ExportXml",
316
                "type": "->"
317
                }
318
            ],
319
            "stackhash": "e6e0b1e1b9d90fee08a5ab8226e485fb"
320
        }
321
        */
322
        foreach ($this->{'errors'} as $error) {
323
            $exception = new stdClass();
324
            $exception->type = $this->decode($error->{'type'} ?? '');
325
            $exception->value = $this->decode($error->{'msg'} ?? '');
326
            $exception->stacktrace = new stdClass();
327
            $exception->stacktrace->frames = [];
328
329
            foreach ($error->{'stackTrace'} as $stack) {
330
                $trace = [
331
                    'platform' => 'php',
332
                    'function' => $stack->{'function'} ?? '',
333
                    'lineno' => (int) ($stack->{'line'} ?? 0),
334
                    'filename' => $error->{'file'} ?? null,
335
                ];
336
                if (isset($stack->{'class'})) {
337
                    $trace['package'] = $stack->{'class'};
338
                }
339
340
                if (isset($stack->{'type'})) {
341
                    $trace['symbol_addr'] = $stack->{'type'};
342
                }
343
344
                if (isset($stack->{'args'})) {// function arguments
345
                    $trace['vars'] = (object) $stack->{'args'};
346
                }
347
348
                $exception->stacktrace->frames[] = $trace;
349
            }
350
351
            $reports[] = [
352
                'platform' => 'php',
353
                'level' => $this->typeToLevel($error->{'type'}),
354
                'exception' => [
355
                    'values' => [$exception],
356
                ],
357
                'message' => $this->decode($error->{'msg'} ?? ''),
358
                'culprit' => $error->{'file'},
359
            ];
360
        }
361
362
        return $reports;
363
    }
364
365 21
    public function getPmaVersion(): string
366
    {
367 21
        return IncidentsTable::getStrippedPmaVersion($this->{'pma_version'} ?? '');
368
    }
369
370
    /**
371
     * @return array<string,mixed>
372
     */
373 14
    public function toJson(): array
374
    {
375 14
        $exType = $this->{'exception_type'} ?? 'js';
376
377
        // array_filter removes keys having null values
378 14
        $release = $this->getPmaVersion();
379
380 14
        return array_filter([
381 14
            'sentry.interfaces.Message' => $this->getUserMessage(),
382 14
            'release' => $release,
383 14
            'dist' => $this->{'pma_version'} ?? '',
384 14
            'platform' => $exType === 'js' ? 'javascript' : 'php',
385 14
            'timestamp' => $this->getTimestampUTC(),
386 14
            'tags' => $this->getTags(),
387 14
            'extra' => $this->getExtras(),
388 14
            'contexts' => $this->getContexts(),
389 14
            'user' => $this->getUser(),
390 14
            'transaction' => $this->getRoute(),
391 14
            'environment' => strpos($release, '-dev') === false ? 'production' : 'development',
392
            //TODO: 'level'
393
        ]);
394
    }
395
396
    /**
397
     * @return array<int,array<string,mixed>>
398
     */
399 14
    public function getReports(): array
400
    {
401 14
        if ($this->isMultiReports()) {
402
            $reports = [];
403
            foreach ($this->getMultiDataToSend() as $data) {
404
                $reports[] = array_merge($this->toJson(), $data, [
405
                    'event_id' => $this->getEventId(),
406
                ]);
407
            }
408
409
            return $reports;
410
        }
411
412
        return [
413 14
            array_merge(
414 14
                $this->toJson(),
415 14
                $this->getExceptionJs(),
416
                [
417 14
                    'event_id' => $this->getEventId(),
418
                ]
419
            ),
420
        ];
421
    }
422
}
423