Completed
Pull Request — master (#170)
by Deven
10:17
created

GithubController::link_issue()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 59
Code Lines 38

Duplication

Lines 5
Ratio 8.47 %

Code Coverage

Tests 37
CRAP Score 8.001

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 5
loc 59
ccs 37
cts 38
cp 0.9737
rs 7.132
c 3
b 0
f 0
cc 8
eloc 38
nc 9
nop 1
crap 8.001

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
/* 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\Network\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 = array('Html', 'Form');
35
36
    public $components = array('GithubApi');
37
38 4
    public function beforeFilter(Event $event)
39
    {
40 4
        parent::beforeFilter($event);
41 4
        $this->GithubApi->githubConfig = Configure::read('GithubConfig');
42 4
        $this->GithubApi->githubRepo = Configure::read('GithubRepoPath');
43 4
    }
44
45
    /**
46
     * create Github Issue.
47
     *
48
     * @param int $reportId
49
     *
50
     * @throws NotFoundException
51
     */
52 1
    public function create_issue($reportId)
53
    {
54 1
        if (!isset($reportId) || !$reportId) {
55
            throw new NotFoundException(__('Invalid report'));
56
        }
57
58 1
        $reportsTable = TableRegistry::get('Reports');
59 1
        $report = $reportsTable->findById($reportId)->all()->first();
60
61 1
        if (!$report) {
62 1
            throw new NotFoundException(__('Invalid report'));
63
        }
64
65 1
        $reportArray = $report->toArray();
66 1
        if (empty($this->request->data)) {
0 ignored issues
show
Deprecated Code introduced by
The property Cake\Http\ServerRequest::$data has been deprecated with message: 3.4.0 This public property will be removed in 4.0.0. Use getData() instead.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
67 1
            $this->set('pma_version', $reportArray['pma_version']);
68 1
            $this->set('error_name', $reportArray['error_name']);
69 1
            $this->set('error_message', $reportArray['error_message']);
70
71 1
            return;
72
        }
73
74 1
        $this->autoRender = false;
75
        $data = array(
76 1
            'title' => $this->request->data['summary'],
0 ignored issues
show
Deprecated Code introduced by
The property Cake\Http\ServerRequest::$data has been deprecated with message: 3.4.0 This public property will be removed in 4.0.0. Use getData() instead.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
77 1
            'labels' => $this->request->data['labels'] ? explode(',', $this->request->data['labels']) : array(),
0 ignored issues
show
Deprecated Code introduced by
The property Cake\Http\ServerRequest::$data has been deprecated with message: 3.4.0 This public property will be removed in 4.0.0. Use getData() instead.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
78
        );
79 1
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
80 1
        $incident = $incidents_query->first();
81 1
        $reportArray['exception_type'] = $incident['exception_type'] ? 'php' : 'js';
82 1
        $reportArray['description'] = $this->request->data['description'];
0 ignored issues
show
Deprecated Code introduced by
The property Cake\Http\ServerRequest::$data has been deprecated with message: 3.4.0 This public property will be removed in 4.0.0. Use getData() instead.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
83
84 1
        $data['body']
85 1
            = $this->_getReportDescriptionText($reportId, $reportArray);
86 1
        $data['labels'][] = 'automated-error-report';
87
88 1
        list($issueDetails, $status) = $this->GithubApi->createIssue(
89 1
            Configure::read('GithubRepoPath'),
90 1
            $data,
91 1
            $this->request->session()->read('access_token')
92
        );
93
94 1
        if ($this->_handleGithubResponse($status, 1, $reportId, $issueDetails['number'])) {
95
            // Update report status
96 1
            $report->status = $this->_getReportStatusFromIssueState($issueDetails['state']);
97 1
            $reportsTable->save($report);
98
99 1
            $this->redirect(array('controller' => 'reports', 'action' => 'view',
100 1
                $reportId, ));
101 View Code Duplication
        } else {
102 1
            $flash_class = 'alert alert-error';
103 1
            $this->Flash->default($this->_getErrors($issueDetails, $status),
104 1
                array('params' => array('class' => $flash_class)));
105
        }
106 1
    }
107
108
    /**
109
     * Links error report to existing issue on Github.
110
     *
111
     * @param mixed $reportId
112
     */
113 1
    public function link_issue($reportId)
114
    {
115 1
        if (!isset($reportId) || !$reportId) {
116
            throw new NotFoundException(__('Invalid reportId'));
117
        }
118
119 1
        $reportsTable = TableRegistry::get('Reports');
120 1
        $report = $reportsTable->findById($reportId)->all()->first();
121
122 1
        if (!$report) {
123 1
            throw new NotFoundException(__('Invalid report'));
124
        }
125
126 1
        $ticket_id = intval($this->request->query['ticket_id']);
0 ignored issues
show
Deprecated Code introduced by
The property Cake\Http\ServerRequest::$query has been deprecated with message: 3.4.0 This public property will be removed in 4.0.0. Use getQuery() or getQueryParams() instead.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
127 1
        if (!$ticket_id) {
128 1
            throw new NotFoundException(__('Invalid Ticket ID!!'));
129
        }
130 1
        $reportArray = $report->toArray();
131
132 1
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
133 1
        $incident = $incidents_query->first();
134 1
        $reportArray['exception_type'] = $incident['exception_type'] ? 'php' : 'js';
135
136 1
        $commentText = $this->_getReportDescriptionText(
137 1
            $reportId, $reportArray
138
        );
139 1
        list($commentDetails, $status) = $this->GithubApi->createComment(
140 1
            Configure::read('GithubRepoPath'),
141 1
            array('body' => $commentText),
142 1
            $ticket_id,
143 1
            $this->request->session()->read('access_token')
144
        );
145 1
        if ($this->_handleGithubResponse($status, 2, $reportId, $ticket_id)) {
146
            // Update report status
147 1
            $report->status = 'forwarded';
148
149 1
            list($issueDetails, $status) = $this->GithubApi->getIssue(
150 1
                Configure::read('GithubRepoPath'),
151 1
                array(),
152 1
                $ticket_id,
153 1
                $this->request->session()->read('access_token')
154
            );
155 1
            if ($this->_handleGithubResponse($status, 4, $reportId, $ticket_id)) {
156
                // If linked Github issue state is available, use it to update Report's status
157 1
                $report->status = $this->_getReportStatusFromIssueState(
158 1
                    $issueDetails['state']
159
                );
160
            }
161
162 1
            $reportsTable->save($report);
163 View Code Duplication
        } else {
164 1
            $flash_class = 'alert alert-error';
165 1
            $this->Flash->default($this->_getErrors($commentDetails, $status),
166 1
                    array('params' => array('class' => $flash_class)));
167
        }
168
169 1
        $this->redirect(array('controller' => 'reports', 'action' => 'view',
170 1
                        $reportId, ));
171 1
    }
172
173
    /**
174
     * Un-links error report to associated issue on Github.
175
     *
176
     * @param mixed $reportId
177
     */
178 1
    public function unlink_issue($reportId)
179
    {
180 1
        if (!isset($reportId) || ! $reportId) {
181
            throw new NotFoundException(__('Invalid reportId'));
182
        }
183
184 1
        $reportsTable = TableRegistry::get('Reports');
185 1
        $report = $reportsTable->findById($reportId)->all()->first();
186
187 1
        if (!$report) {
188 1
            throw new NotFoundException(__('Invalid report'));
189
        }
190
191 1
        $reportArray = $report->toArray();
192 1
        $ticket_id = $reportArray['sourceforge_bug_id'];
193
194 1
        if (!$ticket_id) {
195 1
            throw new NotFoundException(__('Invalid Ticket ID!!'));
196
        }
197
198
        // "formatted" text of the comment.
199
        $commentText = 'This Issue is no longer associated with [Report#'
200 1
            . $reportId
201 1
            . ']('
202 1
            . Router::url('/reports/view/' . $reportId, true)
203 1
            . ')'
204 1
            . "\n\n*This comment is posted automatically by phpMyAdmin's "
205 1
            . '[error-reporting-server](https://reports.phpmyadmin.net).*';
206
207 1
        list($commentDetails, $status) = $this->GithubApi->createComment(
208 1
            Configure::read('GithubRepoPath'),
209 1
            array('body' => $commentText),
210 1
            $ticket_id,
211 1
            $this->request->session()->read('access_token')
212
        );
213
214 1
        if ($this->_handleGithubResponse($status, 3, $reportId)) {
215
            // Update report status
216 1
            $report->status = 'new';
217 1
            $reportsTable->save($report);
218 View Code Duplication
        } else {
219 1
            $flash_class = 'alert alert-error';
220 1
            $this->Flash->default($this->_getErrors($commentDetails, $status),
221 1
                    array('params' => array('class' => $flash_class)));
222
        }
223
224 1
        $this->redirect(array('controller' => 'reports', 'action' => 'view',
225 1
                        $reportId, ));
226 1
    }
227
228
    /**
229
     * Returns pretty error message string.
230
     *
231
     * @param object $response the response returned by Github api
232
     * @param int    $status   status returned by Github API
233
     *
234
     * @return error string
235
     */
236 3
    protected function _getErrors($response, $status)
237
    {
238
        $errorString = 'There were some problems with the issue submission.'
239 3
            . ' Returned status is (' . $status . ')';
240
        $errorString .= '<br/> Here is the dump for the errors field provided by'
241
            . ' github: <br/>'
242
            . '<pre>'
243 3
            . print_r($response, true)
244 3
            . '</pre>';
245
246 3
        return $errorString;
247
    }
248
249
    /**
250
     * Returns the text to be added while creating an issue
251
     *
252
     * @param integer $reportId       Report Id
253
     * @param array   $report         Report associative array
254
     *
255
     * @return string
256
     */
257 2
    protected function _getReportDescriptionText($reportId, $report)
258
    {
259 2
        $incident_count = $this->_getTotalIncidentCount($reportId);
260
261
        // "formatted" text of the comment.
262
        $formattedText
263 2
            = array_key_exists('description', $report) ? $report['description'] . "\n\n"
264 2
                : '';
265
        $formattedText .= "\nParam | Value "
266
            . "\n -----------|--------------------"
267 2
            . "\n Error Type | " . $report['error_name']
268 2
            . "\n Error Message |" . $report['error_message']
269 2
            . "\n Exception Type |" . $report['exception_type']
270 2
            . "\n phpMyAdmin version |" . $report['pma_version']
271 2
            . "\n Incident count | " . $incident_count
272 2
            . "\n Link | [Report#"
273 2
                . $reportId
274 2
                . ']('
275 2
                . Router::url('/reports/view/' . $reportId, true)
276 2
                . ')'
277 2
            . "\n\n*This comment is posted automatically by phpMyAdmin's "
278 2
            . '[error-reporting-server](https://reports.phpmyadmin.net).*';
279
280 2
        return $formattedText;
281
    }
282
283
    /**
284
     * Github Response Handler.
285
     *
286
     * @param int $response  the status returned by Github API
287
     * @param int $type      type of response. 1 for create_issue, 2 for link_issue, 3 for unlink_issue,
288
     *                       1 for create_issue,
289
     *                       2 for link_issue,
290
     *                       3 for unlink_issue,
291
     *                       4 for get_issue
292
     * @param int $report_id report id
293
     * @param int $ticket_id ticket id, required for link ticket only
294
     *
295
     * @return bool value. True on success. False on any type of failure.
296
     */
297 4
    protected function _handleGithubResponse($response, $type, $report_id, $ticket_id = 1)
298
    {
299 4
        if (!in_array($type, array(1, 2, 3, 4))) {
300
            throw new InvalidArgumentException('Invalid Argument "$type".');
301
        }
302
303 4
        $updateReport = true;
304
305 4
        if ($type == 4 && $response == 200) {
306
            // issue details fetched successfully
307 2
            return true;
308 4
        } elseif ($response == 201) {
309
            // success
310
            switch ($type) {
311 3
                case 1:
312 1
                    $msg = 'Github issue has been created for this report.';
313 1
                    break;
314 2
                case 2:
315 1
                    $msg = 'Github issue has been linked with this report.';
316 1
                    break;
317 1
                case 3:
318 1
                    $msg = 'Github issue has been unlinked with this report.';
319 1
                    $ticket_id = null;
320 1
                    break;
321
322
                default:
323
                    $msg = 'Something went wrong!';
324
                    break;
325
            }
326
327 3
            if ($updateReport) {
328 3
                $report = TableRegistry::get('Reports')->get($report_id);
329 3
                $report->sourceforge_bug_id = $ticket_id;
0 ignored issues
show
Bug introduced by
Accessing sourceforge_bug_id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
330 3
                TableRegistry::get('Reports')->save($report);
331
            }
332
333 3 View Code Duplication
            if ($msg !== '') {
334 3
                $flash_class = 'alert alert-success';
335 3
                $this->Flash->default($msg,
336 3
                    array('params' => array('class' => $flash_class)));
337
            }
338
339 3
            return true;
340 4
        } elseif ($response === 403) {
341 1
            $flash_class = 'alert alert-error';
342 1
            $this->Flash->default(
343
                    'Unauthorised access to Github. github'
344
                    . ' credentials may be out of date. Please check and try again'
345 1
                    . ' later.',
346 1
                    array('params' => array('class' => $flash_class)));
347
348 1
            return false;
349 3
        } elseif ($response === 404
350 3
            && $type == 2
351
        ) {
352 1
            $flash_class = 'alert alert-error';
353 1
            $this->Flash->default(
354
                    'Bug Issue not found on Github.'
355 1
                    . ' Are you sure the issue number is correct? Please check and try again!',
356 1
                     array('params' => array('class' => $flash_class)));
357
358 1
            return false;
359
        }
360
361
        // unknown response code
362 2
        $flash_class = 'alert alert-error';
363 2
        $this->Flash->default('Unhandled response code recieved: ' . $response,
364 2
                    array('params' => array('class' => $flash_class)));
365
366 2
        return false;
367
    }
368
369
    /**
370
     * Get Incident counts for a report and
371
     * all its related reports
372
     *
373
     * @param $reportId Report ID
374
     *
375
     * @return $total_incident_count Total Incident count for a report
0 ignored issues
show
Documentation introduced by
The doc-type $total_incident_count could not be parsed: Unknown type name "$total_incident_count" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
376
     */
377 2
    protected function _getTotalIncidentCount($reportId)
378
    {
379 2
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
380 2
        $incident_count = $incidents_query->count();
381
382
        $params_count = array(
383 2
            'fields' => array('inci_count' => 'inci_count'),
384
            'conditions' => array(
385 2
                    'related_to = ' . $reportId,
386
            ),
387
        );
388
        $subquery_params_count = array(
389 2
            'fields' => array(
390
                'report_id' => 'report_id',
391
            ),
392
        );
393 2
        $subquery_count = TableRegistry::get('Incidents')->find(
394 2
            'all', $subquery_params_count
395
        );
396 2
        $inci_count_related = TableRegistry::get('Reports')->find('all', $params_count)->innerJoin(
397 2
                array('incidents' => $subquery_count),
398 2
                array('incidents.report_id = Reports.related_to')
399 2
            )->count();
400
401 2
        return $incident_count + $inci_count_related;
402
    }
403
404
    /**
405
     * Get corresponding report status from Github issue state
406
     *
407
     * @param $issueState Linked Github issue's state
408
     *
409
     * @return Corresponding status to which the linked report should be updated to
410
     */
411 3
    protected function _getReportStatusFromIssueState($issueState)
412
    {
413
        // default
414 3
        $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...
415
        switch ($issueState) {
416 3
            case 'closed':
417 2
                $reportStatus = 'resolved';
418 2
                break;
419
420
            default:
421 3
                $reportStatus = 'forwarded';
422 3
                break;
423
        }
424
425 3
        return $reportStatus;
426
    }
427
428
    /**
429
     * Synchronize Report Statuses from Github issues
430
     *
431
     * To be used as a cron job (using webroot/cron_dispatcher.php).
432
     *
433
     * Can not (& should not) be directly accessed via web.
434
     */
435 1
    public function sync_issue_status()
436
    {
437 1
        if (!defined('CRON_DISPATCHER')) {
438 1
            $flash_class = 'alert alert-error';
439 1
            $this->Flash->default(
440 1
                'Unauthorised action! This action is not available on Web interface',
441 1
                array('params' => array('class' => $flash_class))
442
            );
443
444 1
            $this->redirect('/');
445 1
            return;
446
        }
447
448 1
        $this->autoRender = false;
449 1
        $reportsTable = TableRegistry::get('Reports');
450
451
        // Fetch all linked reports
452 1
        $reports = $reportsTable->find(
453 1
            'all',
454
            array(
455 1
                'conditions' => array(
456
                    'sourceforge_bug_id IS NOT NULL',
457
                    'NOT' => array(
458
                        'status' => 'resolved'
459
                    )
460
                )
461
            )
462
        );
463
464 1
        foreach ($reports as $report) {
465 1
            $report = $report->toArray();
466
467
            // fetch the new issue status
468 1
            list($issueDetails, $status) = $this->GithubApi->getIssue(
469 1
                Configure::read('GithubRepoPath'),
470 1
                array(),
471 1
                $report['sourceforge_bug_id'],
472 1
                Configure::read('GithubAccessToken')
473
            );
474
475 1
            if (!$this->_handleGithubResponse($status, 4, $report['id'], $report['sourceforge_bug_id'])) {
476 1
                Log::error(
477
                    'FAILED: Fetching status of Issue #'
478 1
                        . ($report['sourceforge_bug_id'])
479 1
                        . ' associated with Report#'
480 1
                        . ($report['id'])
481 1
                        . '. Status returned: ' . $status,
482 1
                    ['scope' => 'cron_jobs']
483
                );
484 1
                continue;
485
            }
486
487
            // if Github issue state has changed, update the status of report
488 1
            if ($report['status'] !== $issueDetails['state']) {
489 1
                $rep = $reportsTable->get($report['id']);
490 1
                $rep->status = $this->_getReportStatusFromIssueState($issueDetails['state']);
0 ignored issues
show
Bug introduced by
Accessing status on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
491
492
                // Save the report
493 1
                $reportsTable->save($rep);
494
495 1
                Log::debug(
496
                    'SUCCESS: Updated status of Report #'
497 1
                    . $report['id'] . ' from state of its linked Github issue #'
498 1
                    . $report['sourceforge_bug_id'] . ' to ' . $rep->status,
0 ignored issues
show
Bug introduced by
Accessing status on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
499 1
                    ['scope' => 'cron_jobs']
500
                );
501
            }
502
        }
503 1
    }
504
}
505