Completed
Push — master ( 7c757d...667317 )
by William
02:47
created

ReportsTable::getIncidentsWithDescription()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 5
cts 5
cp 1
rs 9.8666
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/* vim: set expandtab sw=4 ts=4 sts=4: */
4
/**
5
 * Report model representing a group of incidents.
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\Model\Table;
21
22
use Cake\Model\Model;
23
use Cake\ORM\Table;
24
use Cake\ORM\TableRegistry;
25
use Cake\Routing\Router;
26
27
/**
28
 * A report a representing a group of incidents.
29
 */
30
class ReportsTable extends Table
31
{
32
    /**
33
     * @var array
34
     *
35
     * @see http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasmany
36
     * @see Cake::Model::$hasMany
37
     */
38
    public $hasMany = [
39
        'Incidents' => [
40
            'dependant' => true,
41
        ],
42
    ];
43
44
    /**
45
     * @var array
46
     *
47
     * @see http://book.cakephp.org/2.0/en/models/model-attributes.html#validate
48
     * @see http://book.cakephp.org/2.0/en/models/data-validation.html
49
     * @see Model::$validate
50
     */
51
    public $validate = [
52
        'error_message' => [
53
            'rule' => 'notEmpty',
54
            'required' => true,
55
        ],
56
    ];
57
58
    /**
59
     * List of valid finder method options, supplied as the first parameter to find().
60
     *
61
     * @var array
62
     *
63
     * @see Model::$findMethods
64
     */
65
    public $findMethods = [
66
        'allDataTable' => true,
67
        'arrayList' => true,
68
    ];
69
70
    /**
71
     * List of valid finder method options, supplied as the first parameter to find().
72
     *
73
     * @var array
74
     */
75
    public $status = [
76
        'new' => 'New',
77
        'invalid' => 'Invalid',
78
        'resolved' => 'Resolved',
79
        'forwarded' => 'Forwarded',
80
    ];
81
82 25
    public function initialize(array $config)
83
    {
84 25
        $this->hasMany('Incidents', [
85 25
            'dependent' => true,
86
        ]);
87 25
    }
88
89
    /**
90
     * Retrieves the incident records that are related to the current report.
91
     *
92
     * @return array the list of incidents ordered by creation date desc
93
     */
94 4
    public function getIncidents()
95
    {
96 4
        $incidents = TableRegistry::get('Incidents')->find('all', [
97 4
            'limit' => 50,
98 4
            'conditions' => $this->_relatedIncidentsConditions(),
99 4
            'order' => 'Incidents.created desc',
100
        ]);
101
102 4
        return $incidents;
103
    }
104
105
    /**
106
     * Retrieves the report records that are related to the current report.
107
     *
108
     * @return array the list of related reports
109
     */
110 2
    public function getRelatedReports()
111
    {
112 2
        return $this->find('all', [
113 2
            'conditions' => $this->_relatedReportsConditions(),
114
        ]);
115
    }
116
117
    /**
118
     * Retrieves the incident records that are related to the current report that
119
     * also have a description.
120
     *
121
     * @return array the list of incidents ordered by description lenght desc
122
     */
123 3
    public function getIncidentsWithDescription()
124
    {
125 3
        return TableRegistry::get('Incidents')->find('all', [
126 3
            'conditions' => [
127
                'NOT' => [
128
                    'Incidents.steps is null',
129
                ],
130 3
                $this->_relatedIncidentsConditions(),
131
            ],
132 3
            'order' => 'Incidents.steps desc',
133
        ]);
134
    }
135
136
    /**
137
     * Retrieves the incident records that are related to the current report that
138
     * that have a different stacktrace.
139
     *
140
     * @return array the list of incidents
141
     */
142 3
    public function getIncidentsWithDifferentStacktrace()
143
    {
144 3
        return TableRegistry::get('Incidents')->find('all', [
145 3
            'fields' => [
146
                'DISTINCT Incidents.stackhash',
147
                'Incidents.stacktrace',
148
                'Incidents.full_report',
149
                'Incidents.exception_type',
150
            ],
151 3
            'conditions' => $this->_relatedIncidentsConditions(),
152 3
            'group' => 'Incidents.stackhash',
153
        ]);
154
    }
155
156
    /**
157
     * Removes a report from a group of related reports.
158
     *
159
     * @param \Cake\Datasource\EntityInterface $report The report instance
160
     * @return void
161
     */
162 1
    public function removeFromRelatedGroup($report)
163
    {
164 1
        $report->related_to = null;
165 1
        $this->save($report);
166
167 1
        $rel_report = $this->findByRelatedTo($report->id)->first();
168 1
        if ($rel_report) {
169
            $this->updateAll(
170
                ['related_to' => $rel_report->id],
171
                ['related_to' => $report->id]
172
            );
173
        }
174
175
        // remove all redundant self-groupings
176 1
        $this->updateAll(
177 1
            ['related_to' => null],
178 1
            ['reports.related_to = reports.id']
179
        );
180 1
    }
181
182
    /**
183
     * Adds a report to a group of related reports.
184
     *
185
     * @param \Cake\Datasource\EntityInterface $report     The report instance
186
     * @param int                              $related_to The report Id
187
     * @return void
188
     */
189 1
    public function addToRelatedGroup($report, $related_to)
190
    {
191 1
        $dup_report = $this->get($related_to);
192
193 1
        if ($dup_report && $dup_report->related_to) {
194
            $report->related_to = $dup_report->related_to;
195
        } else {
196 1
            $report->related_to = $related_to;
197
        }
198 1
        $report->status = $dup_report->status;
199
200 1
        $this->save($report);
201 1
    }
202
203
    /**
204
     * Returns the full url to the current report.
205
     *
206
     * @return string url
207
     */
208 1
    public function getUrl()
209
    {
210 1
        return Router::url(['controller' => 'reports', 'action' => 'view',
211 1
            $this->id,
212 1
        ], true);
213
    }
214
215
    /**
216
     * groups related incidents by distinct values of a field. It may also provide
217
     * the number of groups, whether to only include incidents that are related
218
     * to the current report and also to only limit the search to incidents
219
     * submited after a certain date.
220
     *
221
     * @param string $fieldName the name of the field to group by
222
     * @param int    $limit     the max number of groups to return
223
     * @param bool   $count     whether to return the number of distinct groups
224
     * @param bool   $related   whether to limit the search to only related incidents
225
     * @param string $timeLimit the date at which to start the search
226
     *
227
     * @return array the groups with the count of each group and possibly the number
228
     *               of groups. Ex: array('Apache' => 2) or array(array('Apache' => 2), 1)
229
     */
230 4
    public function getRelatedByField(
231
        $fieldName,
232
        $limit = 10,
233
        $count = false,
234
        $related = true,
235
        $timeLimit = null
236
    ) {
237 4
        $fieldAlias = "Incidents__$fieldName";
238
        $queryDetails = [
239
            'conditions' => [
240
                'NOT' => [
241 4
                    "Incidents.$fieldName is null",
242
                ],
243
            ],
244 4
            'limit' => $limit,
245
        ];
246
247 4
        if ($related) {
248 3
            $queryDetails['conditions'][] = $this->_relatedIncidentsConditions();
249
        }
250
251 4
        if ($timeLimit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $timeLimit of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
252 2
            $queryDetails['conditions'][] = [
253 2
                'Incidents.created >=' => $timeLimit,
254
            ];
255
        }
256
257 4
        $groupedCount = TableRegistry::get('Incidents')->find('all', $queryDetails);
258
259
        /* Ommit version number in case of browser and server_software fields.
260
         * In case of browser field, version number is seperated by space,
261
         * for example,'FIREFOX 47', hence split the value using space.
262
         * In case of server_software field, version number is seperated by /
263
         * for example, 'nginx/1.7', hence split the value using /.
264
         * See http://book.cakephp.org/3.0/en/orm/query-builder.html#using-sql-functionsp://book.cakephp.org/3.0/en/orm/query-builder.html#using-sql-functions
265
         * for how to use Sql functions with cake
266
         */
267
        switch ($fieldName) {
268 4 View Code Duplication
            case 'browser':
269
                // SUBSTRING(browser, 1, LOCATE(' ', Incidents.browser)-1))
270 3
                $field = $groupedCount->func()->substring([
271 3
                    $fieldName => 'literal',
272 3
                    '1' => 'literal',
273 3
                    "Locate(' ', Incidents.browser)-1" => 'literal',
274
                ]);
275 3
                break;
276 4 View Code Duplication
            case 'server_software':
277
                // SUBSTRING(server_software, 1, LOCATE('/', Incidents.server_software)-1))
278 3
                $field = $groupedCount->func()->substring([
279 3
                    $fieldName => 'literal',
280 3
                    '1' => 'literal',
281 3
                    "Locate('/', Incidents.server_software)-1" => 'literal',
282
                ]);
283 3
                break;
284
            default:
285 4
                $field = $fieldName;
286
        }
287 4
        $groupedCount->select([
288 4
            'count' => $groupedCount->func()->count('*'),
289 4
            $fieldAlias => $field,
290 4
        ])->group($fieldAlias)->distinct(["$fieldAlias"])
291 4
          ->order('count')->toArray();
292
293 4
        if ($count) {
294 3
            $queryDetails['fields'] = ["$fieldName"];
295 3
            $queryDetails['limit'] = null;
296 3
            $queryDetails['group'] = "Incidents.$fieldName";
297 3
            $totalCount = TableRegistry::get('Incidents')->find('all', $queryDetails)->count();
298
299
            return [
300 3
                $groupedCount,
301 3
                $totalCount,
302
            ];
303
        }
304
305 2
        return $groupedCount;
306
    }
307
308
    /**
309
     * Updates the linked reports to a Github issue to newly received status
310
     *
311
     * @param string $issueNumber Github Issue number
312
     * @param string $status      New status to be set
313
     *
314
     * @return int Number of Linked reports updated
315
     */
316 1
    public function setLinkedReportStatus($issueNumber, $status)
317
    {
318
        $conditions = [
319 1
            'sourceforge_bug_id' =>  $issueNumber,
320
        ];
321
        $fields = [
322 1
            'status' => $status,
323
        ];
324
325 1
        return $this->updateAll($fields, $conditions);
326
    }
327
328
    /**
329
     * returns an array of conditions that would return all related incidents to the
330
     * current report.
331
     *
332
     * @return array the related incidents conditions
333
     */
334 7
    protected function _relatedIncidentsConditions()
335
    {
336
        $conditions = [
337 7
            ['Incidents.report_id = ' . $this->id],
338
        ];
339 7
        $report = $this->get($this->id);
340 7
        if ($report->related_to) { //TODO: fix when fix related reports
341 1
            $conditions[] = ['Incidents.report_id = ' . $report->related_to];
342
        }
343
344 7
        return ['OR' => $conditions];
345
    }
346
347
    /**
348
     * returns an array of conditions that would return all related reports to the
349
     * current report.
350
     *
351
     * @return array the related reports conditions
352
     */
353 2
    protected function _relatedReportsConditions()
354
    {
355 2
        $conditions = [['related_to' => $this->id]];
356 2
        $report = $this->get($this->id);
357 2
        if ($report->related_to) { //TODO: fix related to
358
            $conditions[] = ['related_to' => $report->related_to];
359
            $conditions[] = ['id' => $report->related_to];
360
        }
361 2
        $conditions = [['OR' => $conditions]];
362 2
        $conditions[] = ['Reports.id !=' => $this->id];
363
364 2
        return $conditions;
365
    }
366
}
367