Completed
Push — master ( 7c757d...667317 )
by William
02:47
created

GithubController::_getReportDescriptionText()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 17
cts 17
cp 1
rs 9.52
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
4
/**
5
 * Github controller handling issue creation, comments and sync.
6
 *
7
 * phpMyAdmin Error reporting server
8
 * Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
9
 *
10
 * Licensed under The MIT License
11
 * For full copyright and license information, please see the LICENSE.txt
12
 * Redistributions of files must retain the above copyright notice.
13
 *
14
 * @copyright Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
15
 * @license   https://opensource.org/licenses/mit-license.php MIT License
16
 *
17
 * @see      https://www.phpmyadmin.net/
18
 */
19
20
namespace App\Controller;
21
22
use Cake\Core\Configure;
23
use Cake\Event\Event;
24
use Cake\Log\Log;
25
use Cake\Http\Exception\NotFoundException;
26
use Cake\ORM\TableRegistry;
27
use Cake\Routing\Router;
28
29
/**
30
 * Github controller handling github issue submission and creation.
31
 */
32
class GithubController extends AppController
33
{
34
    public $helpers = [
35
        'Html',
36
        'Form',
37
    ];
38
39
    public $components = ['GithubApi'];
40
41 5
    public function beforeFilter(Event $event)
42
    {
43 5
        parent::beforeFilter($event);
44 5
        $this->GithubApi->githubConfig = Configure::read('GithubConfig');
45 5
        $this->GithubApi->githubRepo = Configure::read('GithubRepoPath');
46 5
    }
47
48
    /**
49
     * create Github Issue.
50
     *
51
     * @param int $reportId The report number
52
     *
53
     * @throws NotFoundException
54
     * @return void
55
     */
56 1
    public function create_issue($reportId)
57
    {
58 1
        if (! isset($reportId) || ! $reportId) {
59
            throw new NotFoundException(__('Invalid report'));
60
        }
61
62 1
        $reportsTable = TableRegistry::get('Reports');
63 1
        $report = $reportsTable->findById($reportId)->all()->first();
64
65 1
        if (! $report) {
66 1
            throw new NotFoundException(__('Invalid report'));
67
        }
68
69 1
        $reportArray = $report->toArray();
70 1
        if (empty($this->request->data)) {
71 1
            $this->set('error_name', $reportArray['error_name']);
72 1
            $this->set('error_message', $reportArray['error_message']);
73
74 1
            return;
75
        }
76
77 1
        $this->autoRender = false;
78
        $data = [
79 1
            'title' => $this->request->data['summary'],
80 1
            'labels' => $this->request->data['labels'] ? explode(',', $this->request->data['labels']) : [],
81
        ];
82 1
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
83 1
        $incident = $incidents_query->first();
84 1
        $reportArray['exception_type'] = $incident['exception_type'] ? 'php' : 'js';
85 1
        $reportArray['description'] = $this->request->data['description'];
86
87 1
        $data['body']
88 1
            = $this->_getReportDescriptionText($reportId, $reportArray);
89 1
        $data['labels'][] = 'automated-error-report';
90
91 1
        list($issueDetails, $status) = $this->GithubApi->createIssue(
92 1
            Configure::read('GithubRepoPath'),
93 1
            $data,
94 1
            $this->request->session()->read('access_token')
95
        );
96
97 1
        if ($this->_handleGithubResponse($status, 1, $reportId, $issueDetails['number'])) {
98
            // Update report status
99 1
            $report->status = $this->_getReportStatusFromIssueState($issueDetails['state']);
100 1
            $reportsTable->save($report);
101
102 1
            $this->redirect(['controller' => 'reports', 'action' => 'view',
103 1
                $reportId,
104
            ]);
105 View Code Duplication
        } else {
106 1
            $flash_class = 'alert alert-error';
107 1
            $this->Flash->default(
108 1
                $this->_getErrors($issueDetails, $status),
109 1
                ['params' => ['class' => $flash_class]]
110
            );
111
        }
112 1
    }
113
114
    /**
115
     * Links error report to existing issue on Github.
116
     *
117
     * @param int $reportId The report Id
118
     * @return void
119
     */
120 1
    public function link_issue($reportId)
121
    {
122 1
        if (! isset($reportId) || ! $reportId) {
123
            throw new NotFoundException(__('Invalid reportId'));
124
        }
125
126 1
        $reportsTable = TableRegistry::get('Reports');
127 1
        $report = $reportsTable->findById($reportId)->all()->first();
128
129 1
        if (! $report) {
130 1
            throw new NotFoundException(__('Invalid report'));
131
        }
132
133 1
        $ticket_id = intval($this->request->query['ticket_id']);
134 1
        if (! $ticket_id) {
135 1
            throw new NotFoundException(__('Invalid Ticket ID!!'));
136
        }
137 1
        $reportArray = $report->toArray();
138
139 1
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
140 1
        $incident = $incidents_query->first();
141 1
        $reportArray['exception_type'] = $incident['exception_type'] ? 'php' : 'js';
142
143 1
        $commentText = $this->_getReportDescriptionText(
144 1
            $reportId,
145 1
            $reportArray
146
        );
147 1
        list($commentDetails, $status) = $this->GithubApi->createComment(
148 1
            Configure::read('GithubRepoPath'),
149 1
            ['body' => $commentText],
150 1
            $ticket_id,
151 1
            $this->request->session()->read('access_token')
152
        );
153 1
        if ($this->_handleGithubResponse($status, 2, $reportId, $ticket_id)) {
154
            // Update report status
155 1
            $report->status = 'forwarded';
156
157 1
            list($issueDetails, $status) = $this->GithubApi->getIssue(
158 1
                Configure::read('GithubRepoPath'),
159 1
                [],
160 1
                $ticket_id,
161 1
                $this->request->session()->read('access_token')
162
            );
163 1
            if ($this->_handleGithubResponse($status, 4, $reportId, $ticket_id)) {
164
                // If linked Github issue state is available, use it to update Report's status
165 1
                $report->status = $this->_getReportStatusFromIssueState(
166 1
                    $issueDetails['state']
167
                );
168
            }
169
170 1
            $reportsTable->save($report);
171 View Code Duplication
        } else {
172 1
            $flash_class = 'alert alert-error';
173 1
            $this->Flash->default(
174 1
                $this->_getErrors($commentDetails, $status),
175 1
                ['params' => ['class' => $flash_class]]
176
            );
177
        }
178
179 1
        $this->redirect(['controller' => 'reports', 'action' => 'view',
180 1
            $reportId,
181
        ]);
182 1
    }
183
184
    /**
185
     * Un-links error report to associated issue on Github.
186
     *
187
     * @param int $reportId The report Id
188
     * @return void
189
     */
190 1
    public function unlink_issue($reportId)
191
    {
192 1
        if (! isset($reportId) || ! $reportId) {
193
            throw new NotFoundException(__('Invalid reportId'));
194
        }
195
196 1
        $reportsTable = TableRegistry::get('Reports');
197 1
        $report = $reportsTable->findById($reportId)->all()->first();
198
199 1
        if (! $report) {
200 1
            throw new NotFoundException(__('Invalid report'));
201
        }
202
203 1
        $reportArray = $report->toArray();
204 1
        $ticket_id = $reportArray['sourceforge_bug_id'];
205
206 1
        if (! $ticket_id) {
207 1
            throw new NotFoundException(__('Invalid Ticket ID!!'));
208
        }
209
210
        // "formatted" text of the comment.
211
        $commentText = 'This Issue is no longer associated with [Report#'
212 1
            . $reportId
213 1
            . ']('
214 1
            . Router::url('/reports/view/' . $reportId, true)
215 1
            . ')'
216 1
            . "\n\n*This comment is posted automatically by phpMyAdmin's "
217 1
            . '[error-reporting-server](https://reports.phpmyadmin.net).*';
218
219 1
        list($commentDetails, $status) = $this->GithubApi->createComment(
220 1
            Configure::read('GithubRepoPath'),
221 1
            ['body' => $commentText],
222 1
            $ticket_id,
223 1
            $this->request->session()->read('access_token')
224
        );
225
226 1
        if ($this->_handleGithubResponse($status, 3, $reportId)) {
227
            // Update report status
228 1
            $report->status = 'new';
229 1
            $reportsTable->save($report);
230 View Code Duplication
        } else {
231 1
            $flash_class = 'alert alert-error';
232 1
            $this->Flash->default(
233 1
                $this->_getErrors($commentDetails, $status),
234 1
                ['params' => ['class' => $flash_class]]
235
            );
236
        }
237
238 1
        $this->redirect(['controller' => 'reports', 'action' => 'view',
239 1
            $reportId,
240
        ]);
241 1
    }
242
243
    /**
244
     * Returns pretty error message string.
245
     *
246
     * @param object $response the response returned by Github api
247
     * @param int    $status   status returned by Github API
248
     *
249
     * @return string error string
250
     */
251 3
    protected function _getErrors($response, $status)
252
    {
253
        $errorString = 'There were some problems with the issue submission.'
254 3
            . ' Returned status is (' . $status . ')';
255
        $errorString .= '<br/> Here is the dump for the errors field provided by'
256
            . ' github: <br/>'
257
            . '<pre>'
258 3
            . print_r($response, true)
259 3
            . '</pre>';
260
261 3
        return $errorString;
262
    }
263
264
    /**
265
     * Returns the text to be added while creating an issue
266
     *
267
     * @param integer $reportId Report Id
268
     * @param array   $report   Report associative array
269
     *
270
     * @return string
271
     */
272 2
    protected function _getReportDescriptionText($reportId, $report)
273
    {
274 2
        $incident_count = $this->_getTotalIncidentCount($reportId);
275
276
        // "formatted" text of the comment.
277
        $formattedText
278 2
            = array_key_exists('description', $report) ? $report['description'] . "\n\n"
279 2
                : '';
280
        $formattedText .= "\nParam | Value "
281
            . "\n -----------|--------------------"
282 2
            . "\n Error Type | " . $report['error_name']
283 2
            . "\n Error Message |" . $report['error_message']
284 2
            . "\n Exception Type |" . $report['exception_type']
285 2
            . "\n phpMyAdmin version |" . $report['pma_version']
286 2
            . "\n Incident count | " . $incident_count
287 2
            . "\n Link | [Report#"
288 2
                . $reportId
289 2
                . ']('
290 2
                . Router::url('/reports/view/' . $reportId, true)
291 2
                . ')'
292 2
            . "\n\n*This comment is posted automatically by phpMyAdmin's "
293 2
            . '[error-reporting-server](https://reports.phpmyadmin.net).*';
294
295 2
        return $formattedText;
296
    }
297
298
    /**
299
     * Github Response Handler.
300
     *
301
     * @param int $response  the status returned by Github API
302
     * @param int $type      type of response. 1 for create_issue, 2 for link_issue, 3 for unlink_issue,
303
     *                       1 for create_issue,
304
     *                       2 for link_issue,
305
     *                       3 for unlink_issue,
306
     *                       4 for get_issue
307
     * @param int $report_id report id
308
     * @param int $ticket_id ticket id, required for link ticket only
309
     *
310
     * @return bool value. True on success. False on any type of failure.
311
     */
312 5
    protected function _handleGithubResponse($response, $type, $report_id, $ticket_id = 1)
313
    {
314 5
        if (! in_array($type, [1, 2, 3, 4])) {
315
            throw new \InvalidArgumentException('Invalid Argument "$type".');
316
        }
317
318 5
        $updateReport = true;
319
320 5
        if ($type == 4 && $response == 200) {
321
            // issue details fetched successfully
322 3
            return true;
323 4
        } elseif ($response == 201) {
324
            // success
325
            switch ($type) {
326 3
                case 1:
327 1
                    $msg = 'Github issue has been created for this report.';
328 1
                    break;
329 2
                case 2:
330 1
                    $msg = 'Github issue has been linked with this report.';
331 1
                    break;
332 1
                case 3:
333 1
                    $msg = 'Github issue has been unlinked with this report.';
334 1
                    $ticket_id = null;
335 1
                    break;
336
337
                default:
338
                    $msg = 'Something went wrong!';
339
                    break;
340
            }
341
342 3
            if ($updateReport) {
343 3
                $report = TableRegistry::get('Reports')->get($report_id);
344 3
                $report->sourceforge_bug_id = $ticket_id;
345 3
                TableRegistry::get('Reports')->save($report);
346
            }
347
348 3 View Code Duplication
            if ($msg !== '') {
349 3
                $flash_class = 'alert alert-success';
350 3
                $this->Flash->default(
351 3
                    $msg,
352 3
                    ['params' => ['class' => $flash_class]]
353
                );
354
            }
355
356 3
            return true;
357 4
        } elseif ($response === 403) {
358 1
            $flash_class = 'alert alert-error';
359 1
            $this->Flash->default(
360
                'Unauthorised access to Github. github'
361
                    . ' credentials may be out of date. Please check and try again'
362 1
                    . ' later.',
363 1
                ['params' => ['class' => $flash_class]]
364
            );
365
366 1
            return false;
367 3
        } elseif ($response === 404
368 3
            && $type == 2
369
        ) {
370 1
            $flash_class = 'alert alert-error';
371 1
            $this->Flash->default(
372
                'Bug Issue not found on Github.'
373 1
                    . ' Are you sure the issue number is correct? Please check and try again!',
374 1
                ['params' => ['class' => $flash_class]]
375
            );
376
377 1
            return false;
378
        }
379
380
        // unknown response code
381 2
        $flash_class = 'alert alert-error';
382 2
        $this->Flash->default(
383 2
            'Unhandled response code received: ' . $response,
384 2
            ['params' => ['class' => $flash_class]]
385
        );
386
387 2
        return false;
388
    }
389
390
    /**
391
     * Get Incident counts for a report and
392
     * all its related reports
393
     *
394
     * @param int $reportId The report Id
395
     *
396
     * @return int Total Incident count for a report
397
     */
398 2
    protected function _getTotalIncidentCount($reportId)
399
    {
400 2
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
401 2
        $incident_count = $incidents_query->count();
402
403
        $params_count = [
404 2
            'fields' => ['inci_count' => 'inci_count'],
405
            'conditions' => [
406 2
                'related_to = ' . $reportId,
407
            ],
408
        ];
409
        $subquery_params_count = [
410 2
            'fields' => [
411
                'report_id' => 'report_id',
412
            ],
413
        ];
414 2
        $subquery_count = TableRegistry::get('Incidents')->find(
415 2
            'all',
416 2
            $subquery_params_count
417
        );
418 2
        $inci_count_related = TableRegistry::get('Reports')->find('all', $params_count)->innerJoin(
419 2
            ['incidents' => $subquery_count],
420 2
            ['incidents.report_id = Reports.related_to']
421 2
        )->count();
422
423 2
        return $incident_count + $inci_count_related;
424
    }
425
426
    /**
427
     * Get corresponding report status from Github issue state
428
     *
429
     * @param string $issueState Linked Github issue's state
430
     *
431
     * @return string Corresponding status to which the linked report should be updated to
432
     */
433 4
    protected function _getReportStatusFromIssueState($issueState)
434
    {
435
        // default
436 4
        $reportStatus = '';
0 ignored issues
show
Unused Code introduced by
$reportStatus is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
437
        switch ($issueState) {
438 4
            case 'closed':
439 3
                $reportStatus = 'resolved';
440 3
                break;
441
442
            default:
443 4
                $reportStatus = 'forwarded';
444 4
                break;
445
        }
446
447 4
        return $reportStatus;
448
    }
449
450
    /**
451
     * Synchronize Report Statuses from Github issues
452
     *
453
     * To be used as a cron job (using webroot/cron_dispatcher.php).
454
     *
455
     * Can not (& should not) be directly accessed via web.
456
     * @return void
457
     */
458 2
    public function sync_issue_status()
459
    {
460 2 View Code Duplication
        if (! Configure::read('CronDispatcher')) {
461 1
            $flash_class = 'alert alert-error';
462 1
            $this->Flash->default(
463 1
                'Unauthorised action! This action is not available on Web interface',
464 1
                ['params' => ['class' => $flash_class]]
465
            );
466
467 1
            $this->redirect('/');
468 1
            return;
469
        }
470
471 2
        $this->autoRender = false;
472 2
        $reportsTable = TableRegistry::get('Reports');
473
474
        // Fetch all linked reports
475 2
        $reports = $reportsTable->find(
476 2
            'all',
477
            [
478 2
                'conditions' => [
479
                    'sourceforge_bug_id IS NOT NULL',
480
                    'NOT' => [
481
                        'status' => 'resolved',
482
                    ]
483
                ],
484
            ]
485
        );
486
487 2
        foreach ($reports as $report) {
488 2
            $report = $report->toArray();
489
490
            // fetch the new issue status
491 2
            list($issueDetails, $status) = $this->GithubApi->getIssue(
492 2
                Configure::read('GithubRepoPath'),
493 2
                [],
494 2
                $report['sourceforge_bug_id'],
495 2
                Configure::read('GithubAccessToken')
496
            );
497
498 2
            if (! $this->_handleGithubResponse($status, 4, $report['id'], $report['sourceforge_bug_id'])) {
499 1
                Log::error(
500
                    'FAILED: Fetching status of Issue #'
501 1
                        . ($report['sourceforge_bug_id'])
502 1
                        . ' associated with Report#'
503 1
                        . ($report['id'])
504 1
                        . '. Status returned: ' . $status,
505 1
                    ['scope' => 'cron_jobs']
506
                );
507 1
                continue;
508
            }
509
510
            // if Github issue state has changed, update the status of report
511 2
            if ($report['status'] !== $issueDetails['state']) {
512 2
                $rep = $reportsTable->get($report['id']);
513 2
                $rep->status = $this->_getReportStatusFromIssueState($issueDetails['state']);
514
515
                // Save the report
516 2
                $reportsTable->save($rep);
517
518 2
                Log::debug(
519
                    'SUCCESS: Updated status of Report #'
520 2
                    . $report['id'] . ' from state of its linked Github issue #'
521 2
                    . $report['sourceforge_bug_id'] . ' to ' . $rep->status,
522 2
                    ['scope' => 'cron_jobs']
523
                );
524
            }
525
        }
526 2
    }
527
}
528