Passed
Push — master ( 083ff7...c84473 )
by William
03:02
created

ReportsController::index()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 23
nc 1
nop 0
dl 0
loc 31
ccs 23
cts 23
cp 1
crap 1
rs 9.552
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Reports controller handling reports creation and rendering.
5
 *
6
 * phpMyAdmin Error reporting server
7
 * Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
8
 *
9
 * Licensed under The MIT License
10
 * For full copyright and license information, please see the LICENSE.txt
11
 * Redistributions of files must retain the above copyright notice.
12
 *
13
 * @copyright Copyright (c) phpMyAdmin project (https://www.phpmyadmin.net/)
14
 * @license   https://opensource.org/licenses/mit-license.php MIT License
15
 *
16
 * @see      https://www.phpmyadmin.net/
17
 */
18
19
namespace App\Controller;
20
21
use Cake\Core\Configure;
22
use Cake\Http\Exception\NotFoundException;
23
use Cake\Http\Response;
24
use Cake\Log\Log;
25
use Cake\ORM\TableRegistry;
26
use function __;
27
use function array_key_exists;
28
use function array_push;
29
use function array_unshift;
30
use function count;
31
use function json_encode;
32
use App\Model\Table\ReportsTable;
33
34
/**
35
 * Reports controller handling reports modification and rendering.
36
 *
37
 * @property ReportsTable $Reports
38
 */
39
class ReportsController extends AppController
40
{
41
    /**
42
     * Initialization hook method.
43
     *
44
     * Use this method to add common initialization code like loading components.
45
     *
46
     * @return void Nothing
47
     */
48 49
    public function initialize(): void
49
    {
50 49
        parent::initialize();
51 49
        $this->loadComponent('RequestHandler');
52 49
        $this->loadComponent('OrderSearch');
53 49
        $this->viewBuilder()->setHelpers([
54 49
            'Html',
55
            'Form',
56
            'Reports',
57
            'Incidents',
58
        ]);
59 49
        $this->loadModel('Incidents');
60 49
        $this->loadModel('Reports');
61 49
        $this->loadModel('Notifications');
62 49
        $this->loadModel('Developers');
63 49
    }
64
65 7
    public function index(): void
66
    {
67 7
        $this->Reports->recursive = -1;
68 7
        $this->set(
69 7
            'distinct_statuses',
70 7
            $this->findArrayList(
71 7
                $this->Reports->find()->select(['status'])->distinct(['status']),
72 7
                'status'
73
            )
74
        );
75 7
        $this->set(
76 7
            'distinct_locations',
77 7
            $this->findArrayList(
78 7
                $this->Reports->find()->select(['location'])
79 7
                    ->distinct(['location']),
80 7
                'location'
81
            )
82
        );
83 7
        $this->set(
84 7
            'distinct_versions',
85 7
            $this->findArrayList($this->Reports->find()->select(['pma_version'])->distinct(['pma_version']), 'pma_version')
86
        );
87 7
        $this->set(
88 7
            'distinct_error_names',
89 7
            $this->findArrayList($this->Reports->find('all', [
90 7
                'fields' => ['error_name'],
91
                'conditions' => ['error_name !=' => ''],
92 7
            ])->distinct(['error_name']), 'error_name')
93
        );
94 7
        $this->set('statuses', $this->Reports->status);
95 7
        $this->autoRender = true;
96 7
    }
97
98 7
    public function view(?string $reportId): void
99
    {
100 7
        if (empty($reportId)) {
101
            throw new NotFoundException(__('Invalid report Id.'));
102
        }
103
104 7
        $reportId = (int) $reportId;
105
106 7
        $report = $this->Reports->findById($reportId)->toArray();
107 7
        if (! $report) {
108 7
            throw new NotFoundException(__('The report does not exist.'));
109
        }
110
111 7
        $this->set('report', $report);
112 7
        $this->set('project_name', Configure::read('GithubRepoPath'));
113 7
        $this->Reports->id = $reportId;
114 7
        $this->set('incidents', $this->Reports->getIncidents()->toArray());
115 7
        $this->set(
116 7
            'incidents_with_description',
117 7
            $this->Reports->getIncidentsWithDescription()
118
        );
119 7
        $this->set(
120 7
            'incidents_with_stacktrace',
121 7
            $this->Reports->getIncidentsWithDifferentStacktrace()
122
        );
123 7
        $this->set('related_reports', $this->Reports->getRelatedReports());
124 7
        $this->set('status', $this->Reports->status);
125 7
        $this->setSimilarFields($reportId);
126
127
        // if there is an unread notification for this report, then mark it as read
128 7
        $current_developer = TableRegistry::getTableLocator()->get('Developers')->
129 7
                    findById($this->request->getSession()->read('Developer.id'))->all()->first();
130
131 7
        if (! $current_developer || ! $current_developer['Developer']) {
132 7
            return;
133
        }
134
135
        TableRegistry::getTableLocator()->get('Notifications')->deleteAll(
136
            [
137
                'developer_id' => $current_developer['Developer']['id'],
138
                'report_id' => $reportId,
139
            ]
140
        );
141
    }
142
143 7
    public function data_tables(): ?Response
144
    {
145 2
        $subquery_params = [
146 1
            'fields' => [
147 4
                'report_id' => 'report_id',
148
                'inci_count' => 'COUNT(id)',
149
            ],
150
            'group' => 'report_id',
151
        ];
152 7
        $subquery = TableRegistry::getTableLocator()->get('incidents')->find('all', $subquery_params);
153
154
        // override automatic aliasing, for proper usage in joins
155 2
        $aColumns = [
156 5
            'id' => 'id',
157
            'error_name' => 'error_name',
158
            'error_message' => 'error_message',
159
            'location' => 'location',
160
            'pma_version' => 'pma_version',
161
            'status' => 'status',
162
            'exception_type' => 'exception_type',
163
            'inci_count' => 'inci_count',
164
        ];
165
166 7
        $searchConditions = $this->OrderSearch->getSearchConditions($aColumns, $this->request);
167 7
        $orderConditions = $this->OrderSearch->getOrder($aColumns, $this->request);
168
169 2
        $params = [
170 7
            'fields' => $aColumns,
171
            'conditions' => [
172 7
                $searchConditions,
173 7
                'related_to is NULL',
174
            ],
175 7
            'order' => $orderConditions,
176
        ];
177
178 7
        $pagedParams = $params;
179 7
        $pagedParams['limit'] = (int) $this->request->getQuery('iDisplayLength');
180 7
        $pagedParams['offset'] = (int) $this->request->getQuery('iDisplayStart');
181
182 7
        $rows = $this->findAllDataTable(
183 7
            $this->Reports->find('all', $pagedParams)->innerJoin(
184 7
                ['incidents' => $subquery],
185 7
                ['incidents.report_id = Reports.id']
186
            )
187
        );
188
        //$rows = Sanitize::clean($rows);
189 7
        $totalFiltered = $this->Reports->find('all', $params)->count();
190
191
        // change exception_type from boolean values to strings
192
        // add incident count for related reports
193 7
        $dispRows = [];
194 7
        foreach ($rows as $row) {
195 7
            $row[5] = $this->Reports->status[$row[5]];
196 7
            $row[6] = (int) $row[6] ? 'php' : 'js';
197 2
            $input_elem = "<input type='checkbox' name='reports[]' value='"
198 7
                . $row[0]
199 7
                . "'/>";
200
201 2
            $subquery_params_count = [
202 5
                'fields' => ['report_id' => 'report_id'],
203
            ];
204 7
            $subquery_count = TableRegistry::getTableLocator()->get('incidents')->find(
205 7
                'all',
206 1
                $subquery_params_count
207
            );
208
209 2
            $params_count = [
210 7
                'fields' => ['inci_count' => 'inci_count'],
211
                'conditions' => [
212 7
                    'related_to = ' . $row[0],
213
                ],
214
            ];
215
216 7
            $inci_count_related = $this->Reports->find('all', $params_count)->innerJoin(
217 7
                ['incidents' => $subquery_count],
218 7
                ['incidents.report_id = Reports.related_to']
219 7
            )->count();
220
221 7
            $row[7] += $inci_count_related;
222
223 7
            array_unshift($row, $input_elem);
224 7
            array_push($dispRows, $row);
225
        }
226
227 2
        $response = [
228 7
            'iTotalRecords' => $this->Reports->find('all')->count(),
229 7
            'iTotalDisplayRecords' => $totalFiltered,
230 7
            'sEcho' => (int) $this->request->getQuery('sEcho'),
231 7
            'aaData' => $dispRows,
232
        ];
233 7
        $this->disableAutoRender();
234
235 7
        return $this->response
236 7
            ->withType('application/json')
237 7
            ->withStringBody(json_encode($response));
238
    }
239
240 14
    public function mark_related_to(?string $reportId): void
241
    {
242
        // Only allow POST requests
243 14
        $this->request->allowMethod(['post']);
244
245 14
        $relatedTo = $this->request->getData('related_to');
246 14
        if (! $reportId
247 14
            || ! $relatedTo
248 14
            || $reportId === $relatedTo
249
        ) {
250
            throw new NotFoundException(__('Invalid Report'));
251
        }
252
253 14
        $report = $this->Reports->get($reportId);
254 14
        if (! $report) {
255
            throw new NotFoundException(__('The report does not exist.'));
256
        }
257
258 14
        $this->Reports->addToRelatedGroup($report, $relatedTo);
259
260 14
        $flash_class = 'alert alert-success';
261 14
        $this->Flash->set(
262
            'This report has been marked the same as #'
263 14
                . $relatedTo,
264 14
            ['params' => ['class' => $flash_class]]
265
        );
266 14
        $this->redirect('/reports/view/' . $reportId);
267 14
    }
268
269 7
    public function unmark_related_to(?string $reportId): void
270
    {
271
        // Only allow POST requests
272 7
        $this->request->allowMethod(['post']);
273
274 7
        if (! $reportId) {
275
            throw new NotFoundException(__('Invalid Report'));
276
        }
277
278 7
        $report = $this->Reports->get($reportId);
279 7
        if (! $report) {
280
            throw new NotFoundException(__('The report does not exist.'));
281
        }
282
283 7
        $this->Reports->removeFromRelatedGroup($report);
284
285 7
        $flash_class = 'alert alert-success';
286 7
        $this->Flash->set(
287 7
            'This report has been marked as different.',
288 7
            ['params' => ['class' => $flash_class]]
289
        );
290 7
        $this->redirect('/reports/view/' . $reportId);
291 7
    }
292
293 7
    public function change_state(?string $reportId): void
294
    {
295 7
        if (! $reportId) {
296
            throw new NotFoundException(__('Invalid Report'));
297
        }
298
299 7
        $report = $this->Reports->get($reportId);
300 7
        if (! $report) {
301
            throw new NotFoundException(__('The report does not exist.'));
302
        }
303
304 7
        $state = $this->request->getData('state');
305 7
        $newState = null;
306
307 7
        if (array_key_exists($state, $this->Reports->status)) {
308 7
            $newState = $this->Reports->status[$state];
309
        }
310 7
        if (! $newState) {
311 7
            throw new NotFoundException(__('Invalid State'));
312
        }
313 7
        $report->status = $state;
314 7
        $this->Reports->save($report);
315
316 7
        $flash_class = 'alert alert-success';
317 7
        $this->Flash->set(
318 7
            'The state has been successfully changed.',
319 7
            ['params' => ['class' => $flash_class]]
320
        );
321 7
        $this->redirect('/reports/view/' . $reportId);
322 7
    }
323
324
    /**
325
     * To carry out mass actions on Reports
326
     * Currently only to change their statuses.
327
     * Can be Extended for other mass operations as well.
328
     * Expects an array of Report Ids as a POST parameter.
329
     *
330
     * @return void Nothing
331
     */
332 7
    public function mass_action(): void
333
    {
334 7
        $flash_class = 'alert alert-error';
335 7
        $state = $this->request->getData('state');
336 7
        $newState = null;
337 7
        if (array_key_exists($state, $this->Reports->status)) {
338 7
            $newState = $this->Reports->status[$state];
339
        }
340
341 7
        if (! $newState) {
342 7
            Log::write(
343 7
                'error',
344 7
                'ERRORED: Invalid param "state" in ReportsController::mass_action()',
345 7
                'alert'
346
            );
347 7
            $msg = 'ERROR: Invalid State!!';
348 7
        } elseif (count($this->request->getData('reports')) === 0) {
349 7
            $msg = 'No Reports Selected!! Please Select Reports and try again.';
350
        } else {
351 2
            $msg = "Status has been changed to '"
352 7
                . $this->request->getData('state')
353 7
                . "' for selected Reports!";
354 7
            $flash_class = 'alert alert-success';
355 7
            foreach ($this->request->getData('reports') as $report_id) {
356 7
                $report = $this->Reports->get($report_id);
357 7
                if (! $report) {
358
                    Log::write(
359
                        'error',
360
                        'ERRORED: Invalid report_id in ReportsController::mass_action()',
361
                        'alert'
362
                    );
363
                    $msg = 'ERROR:Invalid Report ID:' . $report_id;
364
                    $flash_class = 'alert alert-error';
365
                    break;
366
                }
367 7
                $report->status = $state;
368 7
                $this->Reports->save($report);
369
            }
370
        }
371
372 7
        $this->Flash->set(
373 7
            $msg,
374 7
            ['params' => ['class' => $flash_class]]
375
        );
376 7
        $this->redirect('/reports/');
377 7
    }
378
379 7
    protected function setSimilarFields(int $id): void
380
    {
381 7
        $this->Reports->id = $id;
382
383 7
        $this->set('columns', TableRegistry::getTableLocator()->get('Incidents')->summarizableFields);
384 7
        $relatedEntries = [];
385
386 7
        foreach (TableRegistry::getTableLocator()->get('Incidents')->summarizableFields as $field) {
387 2
            [$entriesWithCount, $totalEntries] =
388 7
                    $this->Reports->getRelatedByField($field, 25, true);
389 7
            $relatedEntries[$field] = $entriesWithCount;
390 7
            $this->set("${field}_distinct_count", $totalEntries);
391
        }
392
        //error_log(json_encode($relatedEntries));
393 7
        $this->set('related_entries', $relatedEntries);
394 7
    }
395
396
    /**
397
     * @param array|mixed $results The row
398
     * @param string      $key     The search in the row
399
     * @return array Results
400
     */
401 7
    protected function findArrayList($results, string $key): array
402
    {
403 7
        $output = [];
404 7
        foreach ($results as $row) {
405 7
            $output[] = $row[$key];
406
        }
407
408 7
        return $output;
409
    }
410
411
    /**
412
     * @param mixed $results
413
     * @return array
414
     */
415 7
    protected function findAllDataTable($results): array
416
    {
417 7
        $output = [];
418 7
        foreach ($results as $row) {
419 7
            $output_row = [];
420 7
            $row = $row->toArray();
421 7
            foreach ($row as $key => $value) {
422 7
                $output_row[] = $value;
423
            }
424 7
            $output[] = $output_row;
425
        }
426
427 7
        return $output;
428
    }
429
}
430