Completed
Pull Request — master (#151)
by Deven
03:19
created

GithubController::_getTotalIncidentCount()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 0
cts 24
cp 0
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 17
nc 1
nop 1
crap 2
1
<?php
2
3
/* vim: set expandtab sw=4 ts=4 sts=4: */
4
/**
5
 * Sourceforge controller handling source forge ticket submission and creation.
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
    public function beforeFilter(Event $event)
39
    {
40
        parent::beforeFilter($event);
41
        $this->GithubApi->githubConfig = Configure::read('GithubConfig');
42
        $this->GithubApi->githubRepo = Configure::read('GithubRepoPath');
43
    }
44
45
    /**
46
     * create Github Issue.
47
     *
48
     * @param int $reportId
49
     *
50
     * @throws \NotFoundException
51
     * @throws NotFoundException
52
     */
53
    public function create_issue($reportId)
54
    {
55
        if (!$reportId) {
56
            throw new \NotFoundException(__('Invalid report'));
57
        }
58
59
        $report = TableRegistry::get('Reports')->findById($reportId)->toArray();
60
        if (!$report) {
61
            throw new NotFoundException(__('Invalid report'));
62
        }
63
64 View Code Duplication
        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...
65
            $this->set('pma_version', $report[0]['pma_version']);
66
            $this->set('error_name', $report[0]['error_name']);
67
            $this->set('error_message', $report[0]['error_message']);
68
69
            return;
70
        }
71
        $data = array(
72
            '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...
73
            'body' => $this->_augmentDescription(
74
                    $this->request->data['description'], $reportId),
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...
75
            '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...
76
        );
77
        $data['labels'][] = 'automated-error-report';
78
        list($issueDetails, $status) = $this->GithubApi->createIssue(
79
            Configure::read('GithubRepoPath'),
80
            $data,
81
            $this->request->session()->read('access_token')
82
        );
83
84
        if ($this->_handleGithubResponse($status, 1, $reportId, $issueDetails['number'])) {
85
            $this->redirect(array('controller' => 'reports', 'action' => 'view',
86
                $reportId, ));
87
        } else {
88
            $flash_class = 'alert alert-error';
89
            $this->Flash->default(_getErrors($issueDetails, $status),
90
                array('params' => array('class' => $flash_class)));
91
        }
92
    }
93
94
    /**
95
     * Links error report to existing issue on Github.
96
     *
97
     * @param mixed $reportId
98
     */
99
    public function link_issue($reportId)
100
    {
101
        if (!$reportId) {
102
            throw new NotFoundException(__('Invalid reportId'));
103
        }
104
105
        $report = TableRegistry::get('Reports')->findById($reportId)->all()->first();
106
        if (!$report) {
107
            throw new NotFoundException(__('Invalid Report'));
108
        }
109
110
        $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...
111
        if (!$ticket_id) {
112
            throw new NotFoundException(__('Invalid Ticket ID!!'));
113
        }
114
115
        $incidents_query = TableRegistry::get('Incidents')
116
            ->findByReportId($reportId)->all();
117
        $incident = $incidents_query->first();
118
119
        $exception_type = $incident['exception_type'] ? 'php' : 'js';
120
        $incident_count = $this->_getTotalIncidentCount($reportId);
121
122
        // "formatted" text of the comment.
123
        $commentText = 'Param | Value '
124
            . "\n -----------|--------------------"
125
            . "\n Error Type | " . $report['error_name']
126
            . "\n Error Message |" . $report['error_message']
127
            . "\n Exception Type |" . $exception_type
128
            . "\n phpMyAdmin version |" . $report['pma_version']
129
            . "\n Incident count | " . $incident_count
130
            . "\n Link | [Report#"
131
                . $reportId
132
                . ']('
133
                . Router::url('/reports/view/' . $reportId, true)
134
                . ')'
135
            . "\n\n*This comment is posted automatically by phpMyAdmin's "
136
            . '[error-reporting-server](https://reports.phpmyadmin.net).*';
137
138
        list($commentDetails, $status) = $this->GithubApi->createComment(
139
            Configure::read('GithubRepoPath'),
140
            array('body' => $commentText),
141
            $ticket_id,
142
            $this->request->session()->read('access_token')
143
        );
144 View Code Duplication
        if (!$this->_handleGithubResponse($status, 2, $reportId, $ticket_id)) {
145
            $flash_class = 'alert alert-error';
146
            $this->Flash->default(_getErrors($commentDetails, $status),
147
                    array('params' => array('class' => $flash_class)));
148
        }
149
150
        $this->redirect(array('controller' => 'reports', 'action' => 'view',
151
                        $reportId, ));
152
    }
153
154
    /**
155
     * Un-links error report to associated issue on Github.
156
     *
157
     * @param mixed $reportId
158
     */
159
    public function unlink_issue($reportId)
160
    {
161
        if (!$reportId) {
162
            throw new NotFoundException(__('Invalid reportId'));
163
        }
164
165
        $report = TableRegistry::get('Reports')->findById($reportId)->all()->first();
166
        if (!$report) {
167
            throw new NotFoundException(__('Invalid Report'));
168
        }
169
170
        $ticket_id = $report['sourceforge_bug_id'];
171
        if (!$ticket_id) {
172
            throw new NotFoundException(__('Invalid Ticket ID!!'));
173
        }
174
175
        // "formatted" text of the comment.
176
        $commentText = 'This Issue is no longer associated with [Report#'
177
            . $reportId
178
            . ']('
179
            . Router::url('/reports/view/' . $reportId, true)
180
            . ')'
181
            . "\n\n*This comment is posted automatically by phpMyAdmin's "
182
            . '[error-reporting-server](https://reports.phpmyadmin.net).*';
183
184
        list($commentDetails, $status) = $this->GithubApi->createComment(
185
            Configure::read('GithubRepoPath'),
186
            array('body' => $commentText),
187
            $ticket_id,
188
            $this->request->session()->read('access_token')
189
        );
190
191 View Code Duplication
        if (!$this->_handleGithubResponse($status, 3, $reportId)) {
192
            $flash_class = 'alert alert-error';
193
            $this->Flash->default(_getErrors($commentDetails, $status),
194
                    array('params' => array('class' => $flash_class)));
195
        }
196
197
        $this->redirect(array('controller' => 'reports', 'action' => 'view',
198
                        $reportId, ));
199
    }
200
201
    /**
202
     * Returns pretty error message string.
203
     *
204
     * @param object $response the response returned by Github api
205
     * @param int    $status   status returned by Github API
206
     *
207
     * @return error string
208
     */
209
    protected function _getErrors($response, $status)
210
    {
211
        $errorString = 'There were some problems with the issue submission.'
212
            . ' Returned status is (' . $status . ')';
213
        $errorString .= '<br/> Here is the dump for the errors field provided by'
214
            . ' github: <br/>'
215
            . '<pre>'
216
            . print_r($response, true)
217
            . '</pre>';
218
219
        return $errorString;
220
    }
221
222
    /**
223
     * Returns the description with the added string to link to the report.
224
     *
225
     * @param string $description the original description submitted by the dev
226
     * @param string $reportId    the report id relating to the ticket
227
     *
228
     * @return string augmented description
229
     */
230
    protected function _augmentDescription($description, $reportId)
231
    {
232
        $report = TableRegistry::get('Reports');
233
        $report->id = $reportId;
0 ignored issues
show
Bug introduced by
The property id does not seem to exist in Cake\ORM\Table.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
234
        $incident_count = $this->_getTotalIncidentCount($reportId);
235
236
        return "$description\n\n\nThis report is related to user submitted report "
237
            . '[#' . $report->id . '](' . $report->getUrl()
238
            . ') on the phpmyadmin error reporting server.'
239
            . 'It, along with its related reports, has been reported **'
240
            . $incident_count . '** times.';
241
    }
242
243
    /**
244
     * Github Response Handler.
245
     *
246
     * @param int $response  the status returned by Github API
247
     * @param int $type      type of response. 1 for create_issue, 2 for link_issue, 3 for unlink_issue,
248
     *                       1 for create_issue,
249
     *                       2 for link_issue,
250
     *                       3 for unlink_issue,
251
     * @param int $report_id report id
252
     * @param int $ticket_id ticket id, required for link tivket only
253
     *
254
     * @return bool value. True on success. False on any type of failure.
255
     */
256
    protected function _handleGithubResponse($response, $type, $report_id, $ticket_id = 1)
257
    {
258
        if (!in_array($type, array(1, 2, 3))) {
259
            throw new InvalidArgumentException('Invalid Argument "$type".');
260
        }
261
262
        if ($response == 201) {
263
            echo $response;
264
            // success
265
            switch ($type) {
266
                case 1:
267
                    $msg = 'Github issue has been created for this report.';
268
                    break;
269
                case 2:
270
                    $msg = 'Github issue has been linked with this report.';
271
                    break;
272
                case 3:
273
                    $msg = 'Github issue has been unlinked with this report.';
274
                    $ticket_id = null;
275
276
                    break;
277
                default:
278
                    $msg = 'Something went wrong!!';
279
                    break;
280
            }
281
            $report = TableRegistry::get('Reports')->get($report_id);
282
            $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...
283
            TableRegistry::get('Reports')->save($report);
284
            $flash_class = 'alert alert-success';
285
            $this->Flash->default($msg,
286
                array('params' => array('class' => $flash_class)));
287
288
            return true;
289
        } elseif ($response === 403) {
290
            $flash_class = 'alert alert-error';
291
            $this->Flash->default(
292
                    'Unauthorised access to Github. github'
293
                    . ' credentials may be out of date. Please check and try again'
294
                    . ' later.',
295
                    array('params' => array('class' => $flash_class)));
296
297
            return false;
298
        } elseif ($response === 404
299
            && $type == 2
300
        ) {
301
            $flash_class = 'alert alert-error';
302
            $this->Flash->default(
303
                    'Bug Issue not found on Github.'
304
                    . ' Are you sure the issue number is correct? Please check and try again!',
305
                     array('params' => array('class' => $flash_class)));
306
307
            return false;
308
        }
309
            //fail
310
            $flash_class = 'alert alert-error';
311
        $this->Flash->default(json_encode($response),
312
                    array('params' => array('class' => $flash_class)));
313
314
        return false;
315
    }
316
317
    /**
318
     * Get Incident counts for a report and
319
     * all its related reports
320
     *
321
     * @param $reportId Report ID
322
     *
323
     * @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...
324
     */
325
    protected function _getTotalIncidentCount($reportId)
326
    {
327
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
328
        $incident_count = $incidents_query->count();
329
330
        $params_count = array(
331
            'fields' => array('inci_count' => 'inci_count'),
332
            'conditions' => array(
333
                    'related_to = ' . $reportId,
334
            ),
335
        );
336
        $subquery_params_count = array(
337
            'fields' => array(
338
                'report_id' => 'report_id',
339
            ),
340
        );
341
        $subquery_count = TableRegistry::get('Incidents')->find(
342
            'all', $subquery_params_count
343
        );
344
        $inci_count_related = TableRegistry::get('Reports')->find('all', $params_count)->innerJoin(
345
                array('incidents' => $subquery_count),
346
                array('incidents.report_id = Reports.related_to')
347
            )->count();
348
349
        return $incident_count + $inci_count_related;
350
    }
351
352
    /*
353
     * Synchronize Report Statuses from Github issue
354
     * To be used as a cron job.
355
     * Can not (& should not) be directly accessed via Web.
356
     * TODO
357
     */
358
    /* public function sync_issue_statuses(){
359
        if (!defined('CRON_DISPATCHER')) {
360
            $this->redirect('/');
361
            exit();
362
        }
363
364
        $reports = TableRegistry::get('Reports')->find(
365
            'all',
366
            array(
367
                'conditions' => array(
368
                    'NOT' => array(
369
                        'Report.sourceforge_bug_id' => null
370
                    )
371
                )
372
            )
373
        );
374
375
        foreach ($reports as $key => $report) {
376
            $i=0;
377
            // fetch the new ticket status
378
            do {
379
                $new_status = $this->SourceForgeApi->getBugTicketStatus(
380
                    Configure::read('SourceForgeProjectName'),
381
                    $report['sourceforge_bug_id']
382
                );
383
                $i++;
384
            } while($new_status == false && $i <= 3);
385
386
            // if fails all three times, then simply write failure
387
            // into cron_jobs log and move on.
388
            if (!$new_status) {
389
                Log::write(
390
                    'cron_jobs',
391
                    'FAILED: Fetching status of BugTicket#'
392
                        . ($report['sourceforge_bug_id'])
393
                        . ' associated with Report#'
394
                        . ($report['id']),
395
                    'cron_jobs'
396
                );
397
                continue;
398
            }
399
400
            if ($report['status'] != $new_status) {
401
                $rep = TableRegistry::get('Reports')->get($report['id']);
402
                $rep->status = $new_status;
403
                TableRegistry::get('Reports')->save($rep);
404
            }
405
        }
406
        $this->autoRender = false;
407
    } */
408
}
409