Sentry::sendReport()   B
last analyzed

Complexity

Conditions 8
Paths 12

Size

Total Lines 70
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 8
eloc 44
c 4
b 0
f 0
nc 12
nop 1
dl 0
loc 70
ccs 0
cts 47
cp 0
crap 72
rs 7.9715

How to fix   Long Method   

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\Forwarding;
4
5
use App\Report;
6
use Cake\Core\Configure;
7
use Cake\Log\Log;
8
use Composer\CaBundle\CaBundle;
9
use Exception;
10
11
use function curl_errno;
12
use function curl_exec;
13
use function curl_init;
14
use function curl_setopt;
15
use function curl_strerror;
16
use function date_default_timezone_set;
17
use function http_build_query;
18
use function is_array;
19
use function is_dir;
20
use function is_string;
21
use function json_decode;
22
use function json_encode;
23
use function time;
24
25
use const CURLOPT_CAINFO;
26
use const CURLOPT_CAPATH;
27
use const CURLOPT_HTTPHEADER;
28
use const CURLOPT_POST;
29
use const CURLOPT_POSTFIELDS;
30
use const CURLOPT_RETURNTRANSFER;
31
use const CURLOPT_SSL_VERIFYPEER;
32
use const CURLOPT_URL;
33
use const CURLOPT_USERPWD;
34
35
class Sentry
36
{
37
    public static function getSentryTimestamp(): int
38
    {
39
        date_default_timezone_set('UTC');
40
41
        return time();
42
    }
43
44
    /**
45
     * Send report and get ID
46
     *
47
     * @param array<string,mixed> $report The report as an array
48
     * @return string The event ID
49
     *
50
     * @see https://develop.sentry.dev/sdk/store/
51
     */
52
    public static function sendReport(array $report): string
53
    {
54
        $sentryConfig = Configure::read('Forwarding.Sentry');
55
        if ($sentryConfig === null) {
56
            throw new Exception('Missing Sentry config');
57
        }
58
59
        $data = json_encode($report);
60
        $ch = curl_init();
61
        if ($ch === false) {
62
            throw new Exception('Could not init cURL');
63
        }
64
65
        // Use bundled ca file from composer/ca-bundle instead of the system one
66
        $caPathOrFile = CaBundle::getBundledCaBundlePath();
67
68
        if (is_dir($caPathOrFile)) {
69
            curl_setopt($ch, CURLOPT_CAPATH, $caPathOrFile);
70
        } else {
71
            curl_setopt($ch, CURLOPT_CAINFO, $caPathOrFile);
72
        }
73
74
        curl_setopt($ch, CURLOPT_URL, $sentryConfig['base_url'] . '/api/' . $sentryConfig['project_id'] . '/store/');
75
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
76
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
77
        curl_setopt($ch, CURLOPT_USERPWD, $sentryConfig['key'] . ':');
78
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
79
            'Content-type: application/json',
80
            'X-Sentry-Auth: Sentry sentry_version=7, sentry_timestamp=' . self::getSentryTimestamp()
81
            . ', sentry_key=' . $sentryConfig['key'] . ', sentry_client=phpmyadmin-proxy/0.1'
82
            . ', sentry_secret=' . $sentryConfig['secret'],
83
        ]);
84
        curl_setopt($ch, CURLOPT_POST, 1);
85
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
86
87
        $output = curl_exec($ch);
88
89
        $errNo = curl_errno($ch);
90
91
        if ($errNo !== 0) {
92
            $errorMessage = curl_strerror($errNo);
93
            $error = 'Creating the report failed, cURL error (' . $errNo . '): ' . $errorMessage;
94
            Log::error($error);
95
96
            throw new Exception($error);
97
        }
98
99
        if (! is_string($output)) {
100
            $error = 'Creating the report failed: ' . json_encode($output);
101
            Log::error($error);
102
103
            throw new Exception($error);
104
        }
105
106
        $response = json_decode((string) $output, true);
107
        if (! is_array($response)) {
108
            $error = 'Invalid JSON response: ' . json_encode($output);
109
            Log::error($error);
110
111
            throw new Exception($error);
112
        }
113
114
        if (! isset($response['id'])) {
115
            $error = 'Invalid response Id: ' . json_encode($response);
116
            Log::error($error);
117
118
            throw new Exception($error);
119
        }
120
121
        return (string) $response['id'];
122
    }
123
124
    /**
125
     * Set user feedback to Sentry API
126
     *
127
     * @param string $eventId  The event ID
128
     * @param string $comments The comment sent by the user
129
     * @param string $userId   The unique user id
130
     * @return void nothing
131
     */
132
    public static function sendFeedback(string $eventId, string $comments, string $userId): void
133
    {
134
        $sentryConfig = Configure::read('Forwarding.Sentry');
135
        if ($sentryConfig === null) {
136
            throw new Exception('Missing Sentry config');
137
        }
138
139
        $data = [
140
            'comments' => $comments,
141
            'email' => 'u+' . $userId . '@r.local',// 75 chars max (fake hostname to pass email validation rule)
142
            'name' => 'phpMyAdmin User',
143
        ];
144
        $ch = curl_init(
145
            $sentryConfig['base_url'] . '/api/embed/error-page/?eventId=' . $eventId . '&dsn=' . $sentryConfig['dsn_url']
146
        );
147
        if ($ch === false) {
148
            throw new Exception('Could not init cURL');
149
        }
150
151
        curl_setopt($ch, CURLOPT_USERPWD, $sentryConfig['key'] . ':');
152
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
153
        curl_setopt($ch, CURLOPT_POST, 1);
154
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
155
156
        $output = curl_exec($ch);
157
        if (! is_string($output)) {
158
            $error = 'Creating the report feedback failed: ' . json_encode($output);
159
            Log::error($error);
160
161
            return;
162
        }
163
164
        $response = json_decode((string) $output, true);
165
        if (! is_array($response)) {
166
            $error = 'Invalid JSON response: ' . json_encode($output);
167
            Log::error($error);
168
169
            return;
170
        }
171
172
        if (isset($response['errors'])) {
173
            $error = 'Response has errors: ' . json_encode($response['errors']);
174
            Log::error($error);
175
176
            return;
177
        }
178
    }
179
180
    public static function process(Report $report): void
181
    {
182
        foreach ($report->getReports() as $reportData) {
183
            $eventId = self::sendReport($reportData);
184
            if (! $report->hasUserFeedback()) {
185
                continue;
186
            }
187
188
            self::sendFeedback($eventId, $report->getUserFeedback(), $report->getUserId());
189
        }
190
    }
191
}
192