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

ReportsTable::initialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 1
b 0
f 0
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 14
        $report->status = $dup_report->status;
191
192 14
        $this->save($report);
193 14
    }
194
195
    /**
196
     * groups related incidents by distinct values of a field. It may also provide
197
     * the number of groups, whether to only include incidents that are related
198
     * to the current report and also to only limit the search to incidents
199
     * submitted after a certain date.
200
     *
201
     * @param string $fieldName the name of the field to group by
202
     * @param int    $limit     the max number of groups to return
203
     * @param bool   $count     whether to return the number of distinct groups
204
     * @param bool   $related   whether to limit the search to only related incidents
205
     * @param string $timeLimit the date at which to start the search
206
     *
207
     * @return array|object the groups with the count of each group and possibly the number
208
     *               of groups. Ex: array('Apache' => 2) or array(array('Apache' => 2), 1)
209
     */
210 28
    public function getRelatedByField(
211
        string $fieldName,
212
        int $limit = 10,
213
        bool $count = false,
214
        bool $related = true,
215
        ?string $timeLimit = null
216
    ) {
217 28
        $fieldAlias = 'Incidents__' . $fieldName;
218 8
        $queryDetails = [
219
            'conditions' => [
220 28
                'NOT' => ['Incidents.' . $fieldName . ' is null'],
221
            ],
222 28
            'limit' => $limit,
223
        ];
224
225 28
        if ($related) {
226 21
            $queryDetails['conditions'][] = $this->relatedIncidentsConditions();
227
        }
228
229 28
        if ($timeLimit) {
230 14
            $queryDetails['conditions'][] = ['Incidents.created >=' => $timeLimit];
231
        }
232
233 28
        $groupedCount = TableRegistry::getTableLocator()->get('Incidents')->find('all', $queryDetails);
234
235
        /* Ommit version number in case of browser and server_software fields.
236
         * In case of browser field, version number is seperated by space,
237
         * for example,'FIREFOX 47', hence split the value using space.
238
         * In case of server_software field, version number is seperated by /
239
         * for example, 'nginx/1.7', hence split the value using /.
240
         * 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
241
         * for how to use Sql functions with cake
242
         */
243 28
        switch ($fieldName) {
244 28
            case 'browser':
245
                // SUBSTRING(browser, 1, LOCATE(' ', Incidents.browser)-1))
246 21
                $field = $groupedCount->func()->substring([
247 21
                    $fieldName => 'literal',
248 21
                    '1' => 'literal',
249 21
                    "Locate(' ', Incidents.browser)-1" => 'literal',
250
                ]);
251 21
                break;
252 28
            case 'server_software':
253
                // SUBSTRING(server_software, 1, LOCATE('/', Incidents.server_software)-1))
254 21
                $field = $groupedCount->func()->substring([
255 21
                    $fieldName => 'literal',
256 21
                    '1' => 'literal',
257 21
                    "Locate('/', Incidents.server_software)-1" => 'literal',
258
                ]);
259 21
                break;
260
            default:
261 28
                $field = $fieldName;
262
        }
263 28
        $groupedCount->select([
264 28
            'count' => $groupedCount->func()->count('*'),
265 28
            $fieldAlias => $field,
266 28
        ])->group($fieldAlias)->distinct(['' . $fieldAlias . ''])
267 28
          ->order('count')->toArray();
268
269 28
        if ($count) {
270 21
            $queryDetails['fields'] = ['' . $fieldName . ''];
271 21
            $queryDetails['limit'] = null;
272 21
            $queryDetails['group'] = 'Incidents.' . $fieldName;
273 21
            $totalCount = TableRegistry::getTableLocator()->get('Incidents')->find('all', $queryDetails)->count();
274
275
            return [
276 21
                $groupedCount->all(),
277 21
                $totalCount,
278
            ];
279
        }
280
281 14
        return $groupedCount;
282
    }
283
284
    /**
285
     * Updates the linked reports to a Github issue to newly received status
286
     *
287
     * @param string $issueNumber Github Issue number
288
     * @param string $status      New status to be set
289
     *
290
     * @return int Number of Linked reports updated
291
     */
292 7
    public function setLinkedReportStatus(string $issueNumber, string $status): int
293
    {
294 7
        $conditions = ['sourceforge_bug_id' => $issueNumber];
295 7
        $fields = ['status' => $status];
296
297 7
        return $this->updateAll($fields, $conditions);
298
    }
299
300
    /**
301
     * returns an array of conditions that would return all related incidents to the
302
     * current report.
303
     *
304
     * @return array the related incidents conditions
305
     */
306 56
    protected function relatedIncidentsConditions(): array
307
    {
308 16
        $conditions = [
309 56
            ['Incidents.report_id = ' . $this->id],
310
        ];
311 56
        $report = $this->get($this->id);
312 56
        if ($report->related_to) { //TODO: fix when fix related reports
313 14
            $conditions[] = ['Incidents.report_id = ' . $report->related_to];
314
        }
315
316 56
        return ['OR' => $conditions];
317
    }
318
319
    /**
320
     * returns an array of conditions that would return all related reports to the
321
     * current report.
322
     *
323
     * @return array the related reports conditions
324
     */
325 14
    protected function relatedReportsConditions(): array
326
    {
327 14
        $conditions = [['related_to' => $this->id]];
328 14
        $report = $this->get($this->id);
329 14
        if ($report->related_to) { //TODO: fix related to
330
            $conditions[] = ['related_to' => $report->related_to];
331
            $conditions[] = ['id' => $report->related_to];
332
        }
333 14
        $conditions = [['OR' => $conditions]];
334 14
        $conditions[] = ['Reports.id !=' => $this->id];
335
336 14
        return $conditions;
337
    }
338
}
339