ReportsTable   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 310
Duplicated Lines 0 %

Test Coverage

Coverage 94.06%

Importance

Changes 7
Bugs 1 Features 0
Metric Value
eloc 109
dl 0
loc 310
ccs 95
cts 101
cp 0.9406
rs 10
c 7
b 1
f 0
wmc 21

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getIncidents() 0 6 1
A getIncidentsWithDifferentStacktrace() 0 12 1
A removeFromRelatedGroup() 0 17 2
A getIncidentsWithDescription() 0 8 1
A initialize() 0 3 1
A getRelatedReports() 0 4 1
A setLinkedReportStatus() 0 6 1
B getRelatedByField() 0 73 6
A relatedReportsConditions() 0 13 2
A addToRelatedGroup() 0 13 3
A relatedIncidentsConditions() 0 11 2
1
<?php
2
3
/**
4
 * Report model representing a group of incidents.
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\Model\Table;
20
21
use Cake\Datasource\EntityInterface;
22
use Cake\Model\Model;
23
use Cake\ORM\Query;
24
use Cake\ORM\Table;
25
use Cake\ORM\TableRegistry;
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' => ['dependant' => true],
40
    ];
41
42
    /**
43
     * @var array
44
     *
45
     * @see http://book.cakephp.org/2.0/en/models/model-attributes.html#validate
46
     * @see http://book.cakephp.org/2.0/en/models/data-validation.html
47
     * @see Model::$validate
48
     */
49
    public $validate = [
50
        'error_message' => [
51
            'rule' => 'notEmpty',
52
            'required' => true,
53
        ],
54
    ];
55
56
    /**
57
     * List of valid finder method options, supplied as the first parameter to find().
58
     *
59
     * @var array
60
     *
61
     * @see Model::$findMethods
62
     */
63
    public $findMethods = [
64
        'allDataTable' => true,
65
        'arrayList' => true,
66
    ];
67
68
    /**
69
     * List of valid finder method options, supplied as the first parameter to find().
70
     *
71
     * @var array
72
     */
73
    public $status = [
74
        'new' => 'New',
75
        'invalid' => 'Invalid',
76
        'resolved' => 'Resolved',
77
        'forwarded' => 'Forwarded',
78
    ];
79
80 189
    public function initialize(array $config): void
81
    {
82 189
        $this->hasMany('Incidents', ['dependent' => true]);
83 189
    }
84
85
    /**
86
     * Retrieves the incident records that are related to the current report.
87
     *
88
     * @return Query the list of incidents ordered by creation date desc
89
     */
90 35
    public function getIncidents(): Query
91
    {
92 35
        return TableRegistry::getTableLocator()->get('Incidents')->find('all', [
93 35
            'limit' => 50,
94 35
            'conditions' => $this->relatedIncidentsConditions(),
95 35
            'order' => 'Incidents.created desc',
96
        ]);
97
    }
98
99
    /**
100
     * Retrieves the report records that are related to the current report.
101
     *
102
     * @return Query the list of related reports
103
     */
104 14
    public function getRelatedReports(): Query
105
    {
106 14
        return $this->find('all', [
107 14
            'conditions' => $this->relatedReportsConditions(),
108
        ]);
109
    }
110
111
    /**
112
     * Retrieves the incident records that are related to the current report that
113
     * also have a description.
114
     *
115
     * @return Query the list of incidents ordered by description length desc
116
     */
117 21
    public function getIncidentsWithDescription(): Query
118
    {
119 21
        return TableRegistry::getTableLocator()->get('Incidents')->find('all', [
120 3
            'conditions' => [
121 18
                'NOT' => ['Incidents.steps is null'],
122 21
                $this->relatedIncidentsConditions(),
123
            ],
124 21
            'order' => 'Incidents.steps desc',
125
        ]);
126
    }
127
128
    /**
129
     * Retrieves the incident records that are related to the current report that
130
     * that have a different stacktrace.
131
     *
132
     * @return Query the list of incidents
133
     */
134 21
    public function getIncidentsWithDifferentStacktrace(): Query
135
    {
136 21
        return TableRegistry::getTableLocator()->get('Incidents')->find('all', [
137 3
            'fields' => [
138 18
                'Incidents.stackhash',
139
                'Incidents.stacktrace',
140
                'Incidents.full_report',
141
                'Incidents.exception_type',
142
            ],
143 21
            'conditions' => $this->relatedIncidentsConditions(),
144 21
            'group' => 'Incidents.stackhash',
145 21
        ])->distinct(['Incidents.stackhash']);
146
    }
147
148
    /**
149
     * Removes a report from a group of related reports.
150
     *
151
     * @param EntityInterface $report The report instance
152
     * @return void Nothing
153
     */
154 7
    public function removeFromRelatedGroup(EntityInterface $report): void
155
    {
156 7
        $report->related_to = null;
157 7
        $this->save($report);
158
159 7
        $rel_report = $this->findByRelatedTo($report->id)->first();
160 7
        if ($rel_report) {
161
            $this->updateAll(
162
                ['related_to' => $rel_report->id],
163
                ['related_to' => $report->id]
164
            );
165
        }
166
167
        // remove all redundant self-groupings
168 7
        $this->updateAll(
169 7
            ['related_to' => null],
170 7
            ['reports.related_to = reports.id']
171
        );
172 7
    }
173
174
    /**
175
     * Adds a report to a group of related reports.
176
     *
177
     * @param EntityInterface $report     The report instance
178
     * @param int             $related_to The report Id
179
     * @return void Nothing
180
     */
181 14
    public function addToRelatedGroup(EntityInterface $report, int $related_to): void
182
    {
183 14
        $dup_report = $this->get($related_to);
184
185 14
        if ($dup_report && $dup_report->related_to) {
186
            $report->related_to = $dup_report->related_to;
187
        } else {
188 14
            $report->related_to = $related_to;
189
        }
190
191 14
        $report->status = $dup_report->status;
192
193 14
        $this->save($report);
194 14
    }
195
196
    /**
197
     * groups related incidents by distinct values of a field. It may also provide
198
     * the number of groups, whether to only include incidents that are related
199
     * to the current report and also to only limit the search to incidents
200
     * submitted after a certain date.
201
     *
202
     * @param string $fieldName the name of the field to group by
203
     * @param int    $limit     the max number of groups to return
204
     * @param bool   $count     whether to return the number of distinct groups
205
     * @param bool   $related   whether to limit the search to only related incidents
206
     * @param string $timeLimit the date at which to start the search
207
     *
208
     * @return array|object the groups with the count of each group and possibly the number
209
     *               of groups. Ex: array('Apache' => 2) or array(array('Apache' => 2), 1)
210
     */
211 28
    public function getRelatedByField(
212
        string $fieldName,
213
        int $limit = 10,
214
        bool $count = false,
215
        bool $related = true,
216
        ?string $timeLimit = null
217
    ) {
218 28
        $fieldAlias = 'Incidents__' . $fieldName;
219 8
        $queryDetails = [
220
            'conditions' => [
221 28
                'NOT' => ['Incidents.' . $fieldName . ' is null'],
222
            ],
223 28
            'limit' => $limit,
224
        ];
225
226 28
        if ($related) {
227 21
            $queryDetails['conditions'][] = $this->relatedIncidentsConditions();
228
        }
229
230 28
        if ($timeLimit) {
231 14
            $queryDetails['conditions'][] = ['Incidents.created >=' => $timeLimit];
232
        }
233
234 28
        $groupedCount = TableRegistry::getTableLocator()->get('Incidents')->find('all', $queryDetails);
235
236
        /* Ommit version number in case of browser and server_software fields.
237
         * In case of browser field, version number is seperated by space,
238
         * for example,'FIREFOX 47', hence split the value using space.
239
         * In case of server_software field, version number is seperated by /
240
         * for example, 'nginx/1.7', hence split the value using /.
241
         * 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
242
         * for how to use Sql functions with cake
243
         */
244 28
        switch ($fieldName) {
245 28
            case 'browser':
246
                // SUBSTRING(browser, 1, LOCATE(' ', Incidents.browser)-1))
247 21
                $field = $groupedCount->func()->substring([
248 21
                    $fieldName => 'literal',
249 21
                    '1' => 'literal',
250 21
                    "Locate(' ', Incidents.browser)-1" => 'literal',
251
                ]);
252 21
                break;
253 28
            case 'server_software':
254
                // SUBSTRING(server_software, 1, LOCATE('/', Incidents.server_software)-1))
255 21
                $field = $groupedCount->func()->substring([
256 21
                    $fieldName => 'literal',
257 21
                    '1' => 'literal',
258 21
                    "Locate('/', Incidents.server_software)-1" => 'literal',
259
                ]);
260 21
                break;
261
            default:
262 28
                $field = $fieldName;
263
        }
264
265 28
        $groupedCount->select([
266 28
            'count' => $groupedCount->func()->count('*'),
267 28
            $fieldAlias => $field,
268 28
        ])->group($fieldAlias)->distinct(['' . $fieldAlias . ''])
269 28
          ->order('count')->toArray();
270
271 28
        if ($count) {
272 21
            $queryDetails['fields'] = ['' . $fieldName . ''];
273 21
            $queryDetails['limit'] = null;
274 21
            $queryDetails['group'] = 'Incidents.' . $fieldName;
275 21
            $totalCount = TableRegistry::getTableLocator()->get('Incidents')->find('all', $queryDetails)->count();
276
277
            return [
278 21
                $groupedCount->all(),
279 21
                $totalCount,
280
            ];
281
        }
282
283 14
        return $groupedCount;
284
    }
285
286
    /**
287
     * Updates the linked reports to a Github issue to newly received status
288
     *
289
     * @param string $issueNumber Github Issue number
290
     * @param string $status      New status to be set
291
     *
292
     * @return int Number of Linked reports updated
293
     */
294 7
    public function setLinkedReportStatus(string $issueNumber, string $status): int
295
    {
296 7
        $conditions = ['sourceforge_bug_id' => $issueNumber];
297 7
        $fields = ['status' => $status];
298
299 7
        return $this->updateAll($fields, $conditions);
300
    }
301
302
    /**
303
     * returns an array of conditions that would return all related incidents to the
304
     * current report.
305
     *
306
     * @return array the related incidents conditions
307
     */
308 56
    protected function relatedIncidentsConditions(): array
309
    {
310 16
        $conditions = [
311 56
            ['Incidents.report_id = ' . $this->id],
312
        ];
313 56
        $report = $this->get($this->id);
314 56
        if ($report->related_to) { //TODO: fix when fix related reports
315 14
            $conditions[] = ['Incidents.report_id = ' . $report->related_to];
316
        }
317
318 56
        return ['OR' => $conditions];
319
    }
320
321
    /**
322
     * returns an array of conditions that would return all related reports to the
323
     * current report.
324
     *
325
     * @return array the related reports conditions
326
     */
327 14
    protected function relatedReportsConditions(): array
328
    {
329 14
        $conditions = [['related_to' => $this->id]];
330 14
        $report = $this->get($this->id);
331 14
        if ($report->related_to) { //TODO: fix related to
332
            $conditions[] = ['related_to' => $report->related_to];
333
            $conditions[] = ['id' => $report->related_to];
334
        }
335
336 14
        $conditions = [['OR' => $conditions]];
337 14
        $conditions[] = ['Reports.id !=' => $this->id];
338
339 14
        return $conditions;
340
    }
341
}
342