Completed
Pull Request — master (#154)
by Deven
12:19
created

GithubController::_augmentDescription()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 7
Ratio 77.78 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 7
loc 9
rs 9.6666
c 0
b 0
f 0
ccs 0
cts 8
cp 0
cc 1
eloc 6
nc 1
nop 2
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)->first()->toArray();
60
        if (!$report) {
61
            throw new NotFoundException(__('Invalid report'));
62
        }
63
64
        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['pma_version']);
66
            $this->set('error_name', $report['error_name']);
67
            $this->set('error_message', $report['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
            '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...
74
        );
75
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
76
        $incident = $incidents_query->first();
77
        $report['exception_type'] = $incident['exception_type'] ? 'php' : 'js';
78
        $report['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...
79
80
        $data['body']
81
            = $this->_getReportDescriptionText($reportId, $report);
82
        $data['labels'][] = 'automated-error-report';
83
84
        list($issueDetails, $status) = $this->GithubApi->createIssue(
85
            Configure::read('GithubRepoPath'),
86
            $data,
87
            $this->request->session()->read('access_token')
88
        );
89
90
        if ($this->_handleGithubResponse($status, 1, $reportId, $issueDetails['number'])) {
91
            $this->redirect(array('controller' => 'reports', 'action' => 'view',
92
                $reportId, ));
93
        } else {
94
            $flash_class = 'alert alert-error';
95
            $this->Flash->default(_getErrors($issueDetails, $status),
96
                array('params' => array('class' => $flash_class)));
97
        }
98
    }
99
100
    /**
101
     * Links error report to existing issue on Github.
102
     *
103
     * @param mixed $reportId
104
     */
105
    public function link_issue($reportId)
106
    {
107
        if (!$reportId) {
108
            throw new NotFoundException(__('Invalid reportId'));
109
        }
110
111
        $report = TableRegistry::get('Reports')->findById($reportId)->all()->first()->toArray();
112
        if (!$report) {
113
            throw new NotFoundException(__('Invalid Report'));
114
        }
115
116
        $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...
117
        if (!$ticket_id) {
118
            throw new NotFoundException(__('Invalid Ticket ID!!'));
119
        }
120
121
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
122
        $incident = $incidents_query->first();
123
        $report['exception_type'] = $incident['exception_type'] ? 'php' : 'js';
124
125
        $commentText = $this->_getReportDescriptionText(
126
            $reportId, $report
127
        );
128
        list($commentDetails, $status) = $this->GithubApi->createComment(
129
            Configure::read('GithubRepoPath'),
130
            array('body' => $commentText),
131
            $ticket_id,
132
            $this->request->session()->read('access_token')
133
        );
134 View Code Duplication
        if (!$this->_handleGithubResponse($status, 2, $reportId, $ticket_id)) {
135
            $flash_class = 'alert alert-error';
136
            $this->Flash->default(_getErrors($commentDetails, $status),
137
                    array('params' => array('class' => $flash_class)));
138
        }
139
140
        $this->redirect(array('controller' => 'reports', 'action' => 'view',
141
                        $reportId, ));
142
    }
143
144
    /**
145
     * Un-links error report to associated issue on Github.
146
     *
147
     * @param mixed $reportId
148
     */
149
    public function unlink_issue($reportId)
150
    {
151
        if (!$reportId) {
152
            throw new NotFoundException(__('Invalid reportId'));
153
        }
154
155
        $report = TableRegistry::get('Reports')->findById($reportId)->all()->first()->toArray();
156
        if (!$report) {
157
            throw new NotFoundException(__('Invalid Report'));
158
        }
159
160
        $ticket_id = $report['sourceforge_bug_id'];
161
        if (!$ticket_id) {
162
            throw new NotFoundException(__('Invalid Ticket ID!!'));
163
        }
164
165
        // "formatted" text of the comment.
166
        $commentText = 'This Issue is no longer associated with [Report#'
167
            . $reportId
168
            . ']('
169
            . Router::url('/reports/view/' . $reportId, true)
170
            . ')'
171
            . "\n\n*This comment is posted automatically by phpMyAdmin's "
172
            . '[error-reporting-server](https://reports.phpmyadmin.net).*';
173
174
        list($commentDetails, $status) = $this->GithubApi->createComment(
175
            Configure::read('GithubRepoPath'),
176
            array('body' => $commentText),
177
            $ticket_id,
178
            $this->request->session()->read('access_token')
179
        );
180
181 View Code Duplication
        if (!$this->_handleGithubResponse($status, 3, $reportId)) {
182
            $flash_class = 'alert alert-error';
183
            $this->Flash->default(_getErrors($commentDetails, $status),
184
                    array('params' => array('class' => $flash_class)));
185
        }
186
187
        $this->redirect(array('controller' => 'reports', 'action' => 'view',
188
                        $reportId, ));
189
    }
190
191
    /**
192
     * Returns pretty error message string.
193
     *
194
     * @param object $response the response returned by Github api
195
     * @param int    $status   status returned by Github API
196
     *
197
     * @return error string
198
     */
199
    protected function _getErrors($response, $status)
200
    {
201
        $errorString = 'There were some problems with the issue submission.'
202
            . ' Returned status is (' . $status . ')';
203
        $errorString .= '<br/> Here is the dump for the errors field provided by'
204
            . ' github: <br/>'
205
            . '<pre>'
206
            . print_r($response, true)
207
            . '</pre>';
208
209
        return $errorString;
210
    }
211
212
    /**
213
     * Returns the text to be added while creating an issue
214
     *
215
     * @param integer $reportId       Report Id
216
     * @param array   $report         Report associative array
217
     *
218
     * @return string
219
     */
220
    protected function _getReportDescriptionText($reportId, $report)
221
    {
222
        $incident_count = $this->_getTotalIncidentCount($reportId);
223
224
        // "formatted" text of the comment.
225
        $formattedText
226
            = array_key_exists('description', $report) ? $report['description'] . "\n\n"
227
                : '';
228
        $formattedText .= "\nParam | Value "
229
            . "\n -----------|--------------------"
230
            . "\n Error Type | " . $report['error_name']
231
            . "\n Error Message |" . $report['error_message']
232
            . "\n Exception Type |" . $report['exception_type']
233
            . "\n phpMyAdmin version |" . $report['pma_version']
234
            . "\n Incident count | " . $incident_count
235
            . "\n Link | [Report#"
236
                . $reportId
237
                . ']('
238
                . Router::url('/reports/view/' . $reportId, true)
239
                . ')'
240
            . "\n\n*This comment is posted automatically by phpMyAdmin's "
241
            . '[error-reporting-server](https://reports.phpmyadmin.net).*';
242
243
        return $formattedText;
244
    }
245
246
    /**
247
     * Github Response Handler.
248
     *
249
     * @param int $response  the status returned by Github API
250
     * @param int $type      type of response. 1 for create_issue, 2 for link_issue, 3 for unlink_issue,
251
     *                       1 for create_issue,
252
     *                       2 for link_issue,
253
     *                       3 for unlink_issue,
254
     * @param int $report_id report id
255
     * @param int $ticket_id ticket id, required for link tivket only
256
     *
257
     * @return bool value. True on success. False on any type of failure.
258
     */
259
    protected function _handleGithubResponse($response, $type, $report_id, $ticket_id = 1)
260
    {
261
        if (!in_array($type, array(1, 2, 3))) {
262
            throw new InvalidArgumentException('Invalid Argument "$type".');
263
        }
264
265
        if ($response == 201) {
266
            echo $response;
267
            // success
268
            switch ($type) {
269
                case 1:
270
                    $msg = 'Github issue has been created for this report.';
271
                    break;
272
                case 2:
273
                    $msg = 'Github issue has been linked with this report.';
274
                    break;
275
                case 3:
276
                    $msg = 'Github issue has been unlinked with this report.';
277
                    $ticket_id = null;
278
279
                    break;
280
                default:
281
                    $msg = 'Something went wrong!!';
282
                    break;
283
            }
284
            $report = TableRegistry::get('Reports')->get($report_id);
285
            $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...
286
            TableRegistry::get('Reports')->save($report);
287
            $flash_class = 'alert alert-success';
288
            $this->Flash->default($msg,
289
                array('params' => array('class' => $flash_class)));
290
291
            return true;
292
        } elseif ($response === 403) {
293
            $flash_class = 'alert alert-error';
294
            $this->Flash->default(
295
                    'Unauthorised access to Github. github'
296
                    . ' credentials may be out of date. Please check and try again'
297
                    . ' later.',
298
                    array('params' => array('class' => $flash_class)));
299
300
            return false;
301
        } elseif ($response === 404
302
            && $type == 2
303
        ) {
304
            $flash_class = 'alert alert-error';
305
            $this->Flash->default(
306
                    'Bug Issue not found on Github.'
307
                    . ' Are you sure the issue number is correct? Please check and try again!',
308
                     array('params' => array('class' => $flash_class)));
309
310
            return false;
311
        }
312
            //fail
313
            $flash_class = 'alert alert-error';
314
        $this->Flash->default(json_encode($response),
315
                    array('params' => array('class' => $flash_class)));
316
317
        return false;
318
    }
319
320
    /**
321
     * Get Incident counts for a report and
322
     * all its related reports
323
     *
324
     * @param $reportId Report ID
325
     *
326
     * @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...
327
     */
328
    protected function _getTotalIncidentCount($reportId)
329
    {
330
        $incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all();
331
        $incident_count = $incidents_query->count();
332
333
        $params_count = array(
334
            'fields' => array('inci_count' => 'inci_count'),
335
            'conditions' => array(
336
                    'related_to = ' . $reportId,
337
            ),
338
        );
339
        $subquery_params_count = array(
340
            'fields' => array(
341
                'report_id' => 'report_id',
342
            ),
343
        );
344
        $subquery_count = TableRegistry::get('Incidents')->find(
345
            'all', $subquery_params_count
346
        );
347
        $inci_count_related = TableRegistry::get('Reports')->find('all', $params_count)->innerJoin(
348
                array('incidents' => $subquery_count),
349
                array('incidents.report_id = Reports.related_to')
350
            )->count();
351
352
        return $incident_count + $inci_count_related;
353
    }
354
355
    /*
356
     * Synchronize Report Statuses from Github issue
357
     * To be used as a cron job.
358
     * Can not (& should not) be directly accessed via Web.
359
     * TODO
360
     */
361
    /* public function sync_issue_statuses(){
362
        if (!defined('CRON_DISPATCHER')) {
363
            $this->redirect('/');
364
            exit();
365
        }
366
367
        $reports = TableRegistry::get('Reports')->find(
368
            'all',
369
            array(
370
                'conditions' => array(
371
                    'NOT' => array(
372
                        'Report.sourceforge_bug_id' => null
373
                    )
374
                )
375
            )
376
        );
377
378
        foreach ($reports as $key => $report) {
379
            $i=0;
380
            // fetch the new ticket status
381
            do {
382
                $new_status = $this->SourceForgeApi->getBugTicketStatus(
383
                    Configure::read('SourceForgeProjectName'),
384
                    $report['sourceforge_bug_id']
385
                );
386
                $i++;
387
            } while($new_status == false && $i <= 3);
388
389
            // if fails all three times, then simply write failure
390
            // into cron_jobs log and move on.
391
            if (!$new_status) {
392
                Log::write(
393
                    'cron_jobs',
394
                    'FAILED: Fetching status of BugTicket#'
395
                        . ($report['sourceforge_bug_id'])
396
                        . ' associated with Report#'
397
                        . ($report['id']),
398
                    'cron_jobs'
399
                );
400
                continue;
401
            }
402
403
            if ($report['status'] != $new_status) {
404
                $rep = TableRegistry::get('Reports')->get($report['id']);
405
                $rep->status = $new_status;
406
                TableRegistry::get('Reports')->save($rep);
407
            }
408
        }
409
        $this->autoRender = false;
410
    } */
411
}
412