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
|
|
|
$reportsTable = TableRegistry::get('Reports'); |
60
|
|
|
$report = $reportsTable->findById($reportId)->all()->first(); |
61
|
|
|
$reportArray = $report->toArray(); |
62
|
|
|
|
63
|
|
|
if (!$report) { |
64
|
|
|
throw new NotFoundException(__('Invalid report')); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
if (empty($this->request->data)) { |
|
|
|
|
68
|
|
|
$this->set('pma_version', $reportArray['pma_version']); |
69
|
|
|
$this->set('error_name', $reportArray['error_name']); |
70
|
|
|
$this->set('error_message', $reportArray['error_message']); |
71
|
|
|
|
72
|
|
|
return; |
73
|
|
|
} |
74
|
|
|
$data = array( |
75
|
|
|
'title' => $this->request->data['summary'], |
|
|
|
|
76
|
|
|
'labels' => $this->request->data['labels'] ? explode(',', $this->request->data['labels']) : array(), |
|
|
|
|
77
|
|
|
); |
78
|
|
|
$incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all(); |
79
|
|
|
$incident = $incidents_query->first(); |
80
|
|
|
$reportArray['exception_type'] = $incident['exception_type'] ? 'php' : 'js'; |
81
|
|
|
$reportArray['description'] = $this->request->data['description']; |
|
|
|
|
82
|
|
|
|
83
|
|
|
$data['body'] |
84
|
|
|
= $this->_getReportDescriptionText($reportId, $reportArray); |
85
|
|
|
$data['labels'][] = 'automated-error-report'; |
86
|
|
|
|
87
|
|
|
list($issueDetails, $status) = $this->GithubApi->createIssue( |
88
|
|
|
Configure::read('GithubRepoPath'), |
89
|
|
|
$data, |
90
|
|
|
$this->request->session()->read('access_token') |
91
|
|
|
); |
92
|
|
|
|
93
|
|
|
if ($this->_handleGithubResponse($status, 1, $reportId, $issueDetails['number'])) { |
94
|
|
|
// Update report status |
95
|
|
|
$report->status = $this->_getReportStatusFromIssueState($issueDetails['state']); |
96
|
|
|
$reportsTable->save($report); |
97
|
|
|
|
98
|
|
|
$this->redirect(array('controller' => 'reports', 'action' => 'view', |
99
|
|
|
$reportId, )); |
100
|
|
View Code Duplication |
} else { |
101
|
|
|
$flash_class = 'alert alert-error'; |
102
|
|
|
$this->Flash->default(_getErrors($issueDetails, $status), |
103
|
|
|
array('params' => array('class' => $flash_class))); |
104
|
|
|
} |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Links error report to existing issue on Github. |
109
|
|
|
* |
110
|
|
|
* @param mixed $reportId |
111
|
|
|
*/ |
112
|
|
|
public function link_issue($reportId) |
113
|
|
|
{ |
114
|
|
|
if (!$reportId) { |
115
|
|
|
throw new NotFoundException(__('Invalid reportId')); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$reportsTable = TableRegistry::get('Reports'); |
119
|
|
|
$report = $reportsTable->findById($reportId)->all()->first(); |
120
|
|
|
$reportArray = $report->toArray(); |
121
|
|
|
if (!$report) { |
122
|
|
|
throw new NotFoundException(__('Invalid Report')); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
$ticket_id = intval($this->request->query['ticket_id']); |
|
|
|
|
126
|
|
|
if (!$ticket_id) { |
127
|
|
|
throw new NotFoundException(__('Invalid Ticket ID!!')); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
$incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all(); |
131
|
|
|
$incident = $incidents_query->first(); |
132
|
|
|
$reportArray['exception_type'] = $incident['exception_type'] ? 'php' : 'js'; |
133
|
|
|
|
134
|
|
|
$commentText = $this->_getReportDescriptionText( |
135
|
|
|
$reportId, $reportArray |
136
|
|
|
); |
137
|
|
|
list($commentDetails, $status) = $this->GithubApi->createComment( |
138
|
|
|
Configure::read('GithubRepoPath'), |
139
|
|
|
array('body' => $commentText), |
140
|
|
|
$ticket_id, |
141
|
|
|
$this->request->session()->read('access_token') |
142
|
|
|
); |
143
|
|
|
if ($this->_handleGithubResponse($status, 2, $reportId, $ticket_id)) { |
144
|
|
|
// Update report status |
145
|
|
|
$report->status = 'forwarded'; |
146
|
|
|
|
147
|
|
|
list($issueDetails, $status) = $this->GithubApi->getIssue( |
148
|
|
|
Configure::read('GithubRepoPath'), |
149
|
|
|
array(), |
150
|
|
|
$ticket_id, |
151
|
|
|
$this->request->session()->read('access_token') |
152
|
|
|
); |
153
|
|
|
if ($this->_handleGithubResponse($status, 4, $reportId, $ticket_id)) { |
154
|
|
|
// If linked Github issue state is available, use it to update Report's status |
155
|
|
|
$report->status = $this->_getReportStatusFromIssueState( |
156
|
|
|
$issueDetails['state'] |
157
|
|
|
); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
$reportsTable->save($report); |
161
|
|
View Code Duplication |
} else { |
162
|
|
|
$flash_class = 'alert alert-error'; |
163
|
|
|
$this->Flash->default(_getErrors($commentDetails, $status), |
164
|
|
|
array('params' => array('class' => $flash_class))); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
$this->redirect(array('controller' => 'reports', 'action' => 'view', |
168
|
|
|
$reportId, )); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Un-links error report to associated issue on Github. |
173
|
|
|
* |
174
|
|
|
* @param mixed $reportId |
175
|
|
|
*/ |
176
|
|
|
public function unlink_issue($reportId) |
177
|
|
|
{ |
178
|
|
|
if (!$reportId) { |
179
|
|
|
throw new NotFoundException(__('Invalid reportId')); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$reportsTable = TableRegistry::get('Reports'); |
183
|
|
|
$report = $reportsTable->findById($reportId)->all()->first(); |
184
|
|
|
$reportArray = $report->toArray(); |
185
|
|
|
|
186
|
|
|
if (!$report) { |
187
|
|
|
throw new NotFoundException(__('Invalid Report')); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$ticket_id = $reportArray['sourceforge_bug_id']; |
191
|
|
|
if (!$ticket_id) { |
192
|
|
|
throw new NotFoundException(__('Invalid Ticket ID!!')); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
// "formatted" text of the comment. |
196
|
|
|
$commentText = 'This Issue is no longer associated with [Report#' |
197
|
|
|
. $reportId |
198
|
|
|
. '](' |
199
|
|
|
. Router::url('/reports/view/' . $reportId, true) |
200
|
|
|
. ')' |
201
|
|
|
. "\n\n*This comment is posted automatically by phpMyAdmin's " |
202
|
|
|
. '[error-reporting-server](https://reports.phpmyadmin.net).*'; |
203
|
|
|
|
204
|
|
|
list($commentDetails, $status) = $this->GithubApi->createComment( |
205
|
|
|
Configure::read('GithubRepoPath'), |
206
|
|
|
array('body' => $commentText), |
207
|
|
|
$ticket_id, |
208
|
|
|
$this->request->session()->read('access_token') |
209
|
|
|
); |
210
|
|
|
|
211
|
|
|
if ($this->_handleGithubResponse($status, 3, $reportId)) { |
212
|
|
|
// Update report status |
213
|
|
|
$report->status = 'new'; |
214
|
|
|
$reportsTable->save($report); |
215
|
|
View Code Duplication |
} else { |
216
|
|
|
$flash_class = 'alert alert-error'; |
217
|
|
|
$this->Flash->default(_getErrors($commentDetails, $status), |
218
|
|
|
array('params' => array('class' => $flash_class))); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
$this->redirect(array('controller' => 'reports', 'action' => 'view', |
222
|
|
|
$reportId, )); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Returns pretty error message string. |
227
|
|
|
* |
228
|
|
|
* @param object $response the response returned by Github api |
229
|
|
|
* @param int $status status returned by Github API |
230
|
|
|
* |
231
|
|
|
* @return error string |
232
|
|
|
*/ |
233
|
|
|
protected function _getErrors($response, $status) |
234
|
|
|
{ |
235
|
|
|
$errorString = 'There were some problems with the issue submission.' |
236
|
|
|
. ' Returned status is (' . $status . ')'; |
237
|
|
|
$errorString .= '<br/> Here is the dump for the errors field provided by' |
238
|
|
|
. ' github: <br/>' |
239
|
|
|
. '<pre>' |
240
|
|
|
. print_r($response, true) |
241
|
|
|
. '</pre>'; |
242
|
|
|
|
243
|
|
|
return $errorString; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Returns the text to be added while creating an issue |
248
|
|
|
* |
249
|
|
|
* @param integer $reportId Report Id |
250
|
|
|
* @param array $report Report associative array |
251
|
|
|
* |
252
|
|
|
* @return string |
253
|
|
|
*/ |
254
|
|
|
protected function _getReportDescriptionText($reportId, $report) |
255
|
|
|
{ |
256
|
|
|
$incident_count = $this->_getTotalIncidentCount($reportId); |
257
|
|
|
|
258
|
|
|
// "formatted" text of the comment. |
259
|
|
|
$formattedText |
260
|
|
|
= array_key_exists('description', $report) ? $report['description'] . "\n\n" |
261
|
|
|
: ''; |
262
|
|
|
$formattedText .= "\nParam | Value " |
263
|
|
|
. "\n -----------|--------------------" |
264
|
|
|
. "\n Error Type | " . $report['error_name'] |
265
|
|
|
. "\n Error Message |" . $report['error_message'] |
266
|
|
|
. "\n Exception Type |" . $report['exception_type'] |
267
|
|
|
. "\n phpMyAdmin version |" . $report['pma_version'] |
268
|
|
|
. "\n Incident count | " . $incident_count |
269
|
|
|
. "\n Link | [Report#" |
270
|
|
|
. $reportId |
271
|
|
|
. '](' |
272
|
|
|
. Router::url('/reports/view/' . $reportId, true) |
273
|
|
|
. ')' |
274
|
|
|
. "\n\n*This comment is posted automatically by phpMyAdmin's " |
275
|
|
|
. '[error-reporting-server](https://reports.phpmyadmin.net).*'; |
276
|
|
|
|
277
|
|
|
return $formattedText; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Github Response Handler. |
282
|
|
|
* |
283
|
|
|
* @param int $response the status returned by Github API |
284
|
|
|
* @param int $type type of response. 1 for create_issue, 2 for link_issue, 3 for unlink_issue, |
285
|
|
|
* 1 for create_issue, |
286
|
|
|
* 2 for link_issue, |
287
|
|
|
* 3 for unlink_issue, |
288
|
|
|
* 4 for get_issue |
289
|
|
|
* @param int $report_id report id |
290
|
|
|
* @param int $ticket_id ticket id, required for link ticket only |
291
|
|
|
* |
292
|
|
|
* @return bool value. True on success. False on any type of failure. |
293
|
|
|
*/ |
294
|
|
|
protected function _handleGithubResponse($response, $type, $report_id, $ticket_id = 1) |
295
|
|
|
{ |
296
|
|
|
if (!in_array($type, array(1, 2, 3, 4))) { |
297
|
|
|
throw new InvalidArgumentException('Invalid Argument "$type".'); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
$updateReport = true; |
301
|
|
|
|
302
|
|
|
if ($type == 4 && $response == 200) { |
303
|
|
|
// issue details fetched successfully |
304
|
|
|
return true; |
305
|
|
|
} elseif ($response == 201) { |
306
|
|
|
// success |
307
|
|
|
switch ($type) { |
308
|
|
|
case 1: |
309
|
|
|
$msg = 'Github issue has been created for this report.'; |
310
|
|
|
break; |
311
|
|
|
case 2: |
312
|
|
|
$msg = 'Github issue has been linked with this report.'; |
313
|
|
|
break; |
314
|
|
|
case 3: |
315
|
|
|
$msg = 'Github issue has been unlinked with this report.'; |
316
|
|
|
$ticket_id = null; |
317
|
|
|
break; |
318
|
|
|
|
319
|
|
|
default: |
320
|
|
|
$msg = 'Something went wrong!'; |
321
|
|
|
break; |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
if ($updateReport) { |
325
|
|
|
$report = TableRegistry::get('Reports')->get($report_id); |
326
|
|
|
$report->sourceforge_bug_id = $ticket_id; |
|
|
|
|
327
|
|
|
TableRegistry::get('Reports')->save($report); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
View Code Duplication |
if ($msg !== '') { |
331
|
|
|
$flash_class = 'alert alert-success'; |
332
|
|
|
$this->Flash->default($msg, |
333
|
|
|
array('params' => array('class' => $flash_class))); |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
return true; |
337
|
|
|
} elseif ($response === 403) { |
338
|
|
|
$flash_class = 'alert alert-error'; |
339
|
|
|
$this->Flash->default( |
340
|
|
|
'Unauthorised access to Github. github' |
341
|
|
|
. ' credentials may be out of date. Please check and try again' |
342
|
|
|
. ' later.', |
343
|
|
|
array('params' => array('class' => $flash_class))); |
344
|
|
|
|
345
|
|
|
return false; |
346
|
|
|
} elseif ($response === 404 |
347
|
|
|
&& $type == 2 |
348
|
|
|
) { |
349
|
|
|
$flash_class = 'alert alert-error'; |
350
|
|
|
$this->Flash->default( |
351
|
|
|
'Bug Issue not found on Github.' |
352
|
|
|
. ' Are you sure the issue number is correct? Please check and try again!', |
353
|
|
|
array('params' => array('class' => $flash_class))); |
354
|
|
|
|
355
|
|
|
return false; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
// unknown response code |
359
|
|
|
$flash_class = 'alert alert-error'; |
360
|
|
|
$this->Flash->default('Unhandled response code recieved: ' . $response, |
361
|
|
|
array('params' => array('class' => $flash_class))); |
362
|
|
|
|
363
|
|
|
return false; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Get Incident counts for a report and |
368
|
|
|
* all its related reports |
369
|
|
|
* |
370
|
|
|
* @param $reportId Report ID |
371
|
|
|
* |
372
|
|
|
* @return $total_incident_count Total Incident count for a report |
|
|
|
|
373
|
|
|
*/ |
374
|
|
|
protected function _getTotalIncidentCount($reportId) |
375
|
|
|
{ |
376
|
|
|
$incidents_query = TableRegistry::get('Incidents')->findByReportId($reportId)->all(); |
377
|
|
|
$incident_count = $incidents_query->count(); |
378
|
|
|
|
379
|
|
|
$params_count = array( |
380
|
|
|
'fields' => array('inci_count' => 'inci_count'), |
381
|
|
|
'conditions' => array( |
382
|
|
|
'related_to = ' . $reportId, |
383
|
|
|
), |
384
|
|
|
); |
385
|
|
|
$subquery_params_count = array( |
386
|
|
|
'fields' => array( |
387
|
|
|
'report_id' => 'report_id', |
388
|
|
|
), |
389
|
|
|
); |
390
|
|
|
$subquery_count = TableRegistry::get('Incidents')->find( |
391
|
|
|
'all', $subquery_params_count |
392
|
|
|
); |
393
|
|
|
$inci_count_related = TableRegistry::get('Reports')->find('all', $params_count)->innerJoin( |
394
|
|
|
array('incidents' => $subquery_count), |
395
|
|
|
array('incidents.report_id = Reports.related_to') |
396
|
|
|
)->count(); |
397
|
|
|
|
398
|
|
|
return $incident_count + $inci_count_related; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Get corresponding report status from Github issue state |
403
|
|
|
* |
404
|
|
|
* @param $issueState Linked Github issue's state |
405
|
|
|
* |
406
|
|
|
* @return Corresponding status to which the linked report should be updated to |
407
|
|
|
*/ |
408
|
|
|
protected function _getReportStatusFromIssueState($issueState) |
409
|
|
|
{ |
410
|
|
|
// default |
411
|
|
|
$reportStatus = ''; |
|
|
|
|
412
|
|
|
switch ($issueState) { |
413
|
|
|
case 'closed': |
414
|
|
|
$reportStatus = 'resolved'; |
415
|
|
|
break; |
416
|
|
|
|
417
|
|
|
default: |
418
|
|
|
$reportStatus = 'forwarded'; |
419
|
|
|
break; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
return $reportStatus; |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* Synchronize Report Statuses from Github issues |
427
|
|
|
* |
428
|
|
|
* To be used as a cron job (using webroot/cron_dispatcher.php). |
429
|
|
|
* |
430
|
|
|
* Can not (& should not) be directly accessed via web. |
431
|
|
|
*/ |
432
|
|
|
public function sync_issue_status() |
433
|
|
|
{ |
434
|
|
|
|
435
|
|
|
if (!defined('CRON_DISPATCHER')) { |
436
|
|
|
$flash_class = 'alert alert-error'; |
437
|
|
|
$this->Flash->default( |
438
|
|
|
'Unauthorised action! This action is not available on Web interface', |
439
|
|
|
array('params' => array('class' => $flash_class)) |
440
|
|
|
); |
441
|
|
|
|
442
|
|
|
$this->redirect('/'); |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
$this->autoRender = false; |
446
|
|
|
$reportsTable = TableRegistry::get('Reports'); |
447
|
|
|
|
448
|
|
|
// Fetch all linked reports |
449
|
|
|
$reports = $reportsTable->find( |
450
|
|
|
'all', |
451
|
|
|
array( |
452
|
|
|
'conditions' => array( |
453
|
|
|
'sourceforge_bug_id IS NOT NULL' |
454
|
|
|
) |
455
|
|
|
) |
456
|
|
|
); |
457
|
|
|
|
458
|
|
|
foreach ($reports as $report) { |
459
|
|
|
$report = $report->toArray(); |
460
|
|
|
|
461
|
|
|
// fetch the new issue status |
462
|
|
|
list($issueDetails, $status) = $this->GithubApi->getIssue( |
463
|
|
|
Configure::read('GithubRepoPath'), |
464
|
|
|
array(), |
465
|
|
|
$report['sourceforge_bug_id'], |
466
|
|
|
Configure::read('GithubAccessToken') |
467
|
|
|
); |
468
|
|
|
|
469
|
|
|
if (!$this->_handleGithubResponse($status, 4, $report['id'], $report['sourceforge_bug_id'])) { |
470
|
|
|
Log::error( |
471
|
|
|
'FAILED: Fetching status of Issue #' |
472
|
|
|
. ($report['sourceforge_bug_id']) |
473
|
|
|
. ' associated with Report#' |
474
|
|
|
. ($report['id']) |
475
|
|
|
. '. Status returned: ' . $status, |
476
|
|
|
['scope' => 'cron_jobs'] |
477
|
|
|
); |
478
|
|
|
continue; |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
// if Github issue state has changed, update the status of report |
482
|
|
|
if ($report['status'] !== $issueDetails['state']) { |
483
|
|
|
$rep = $reportsTable->get($report['id']); |
484
|
|
|
$rep->status = $this->_getReportStatusFromIssueState($issueDetails['state']); |
|
|
|
|
485
|
|
|
|
486
|
|
|
// Save the report |
487
|
|
|
$reportsTable->save($rep); |
488
|
|
|
|
489
|
|
|
Log::debug( |
490
|
|
|
'SUCCESS: Updated status of Report #' |
491
|
|
|
. $report['id'] . ' from state of its linked Github issue #' |
492
|
|
|
. $report['sourceforge_bug_id'] . ' to ' . $rep->status, |
|
|
|
|
493
|
|
|
['scope' => 'cron_jobs'] |
494
|
|
|
); |
495
|
|
|
} |
496
|
|
|
} |
497
|
|
|
} |
498
|
|
|
} |
499
|
|
|
|
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.