Completed
Push — master ( 7f3e63...891bdf )
by Michal
02:13
created

IncidentsTable::getStackHash()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
ccs 9
cts 9
cp 1
cc 4
eloc 9
nc 4
nop 1
crap 4
1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
4
/**
5
 * An incident a representing a single incident of a submited bug.
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\Log\Log;
23
use Cake\Model\Model;
24
use Cake\ORM\Table;
25
use Cake\ORM\TableRegistry;
26
27
/**
28
 * An incident a representing a single incident of a submited bug.
29
 */
30
class IncidentsTable extends Table
31
{
32
    /**
33
     * @var array
34
     *
35
     * @see http://book.cakephp.org/2.0/en/models/behaviors.html#using-behaviors
36
     * @see Model::$actsAs
37
     */
38
    public $actsAs = array('Summarizable');
39
40
    /**
41
     * @var array
42
     *
43
     * @see http://book.cakephp.org/2.0/en/models/model-attributes.html#validate
44
     * @see http://book.cakephp.org/2.0/en/models/data-validation.html
45
     * @see Model::$validate
46
     */
47
    public $validate = array(
48
        'pma_version' => array(
49
            'rule' => 'notEmpty',
50
            'required' => true,
51
        ),
52
        'php_version' => array(
53
            'rule' => 'notEmpty',
54
            'required' => true,
55
        ),
56
        'full_report' => array(
57
            'rule' => 'notEmpty',
58
            'required' => true,
59
        ),
60
        'stacktrace' => array(
61
            'rule' => 'notEmpty',
62
            'required' => true,
63
        ),
64
        'browser' => array(
65
            'rule' => 'notEmpty',
66
            'required' => true,
67
        ),
68
        'stackhash' => array(
69
            'rule' => 'notEmpty',
70
            'required' => true,
71
        ),
72
        'user_os' => array(
73
            'rule' => 'notEmpty',
74
            'required' => true,
75
        ),
76
        'locale' => array(
77
            'rule' => 'notEmpty',
78
            'required' => true,
79
        ),
80
        'script_name' => array(
81
            'rule' => 'notEmpty',
82
            'required' => true,
83
        ),
84
        'server_software' => array(
85
            'rule' => 'notEmpty',
86
            'required' => true,
87
        ),
88
        'configuration_storage' => array(
89
            'rule' => 'notEmpty',
90
            'required' => true,
91
        ),
92
    );
93
94
    /**
95
     * @var array
96
     *
97
     * @see http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#belongsto
98
     * @see Model::$belongsTo
99
     */
100
101
    /**
102
     * The fields which are summarized in the report page with charts and are also
103
     * used in the overall stats and charts for the website.
104
     *
105
     * @var array
106
     */
107
    public $summarizableFields = array(
108
        'browser', 'pma_version', 'php_version',
109
        'locale', 'server_software', 'user_os', 'script_name',
110
        'configuration_storage',
111
    );
112
113 27
    public function __construct($id = false, $table = null, $ds = null)
114
    {
115 27
        parent::__construct($id, $table, $ds);
0 ignored issues
show
Unused Code introduced by
The call to Table::__construct() has too many arguments starting with $table.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
116
117 27
        $this->filterTimes = array(
0 ignored issues
show
Bug introduced by
The property filterTimes does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
118 27
            'all_time' => array(
119
                'label' => 'All Time',
120
                'limit' => null,
121
                'group' => "DATE_FORMAT(Incidents.created, '%m %Y')",
122
            ),
123
            'day' => array(
124 27
                'label' => 'Last Day',
125 27
                'limit' => date('Y-m-d', strtotime('-1 day')),
126 27
                'group' => "DATE_FORMAT(Incidents.created, '%a %b %d %Y %H')",
127
            ),
128
            'week' => array(
129 27
                'label' => 'Last Week',
130 27
                'limit' => date('Y-m-d', strtotime('-1 week')),
131 27
                'group' => "DATE_FORMAT(Incidents.created, '%a %b %d %Y')",
132
            ),
133
            'month' => array(
134 27
                'label' => 'Last Month',
135 27
                'limit' => date('Y-m-d', strtotime('-1 month')),
136 27
                'group' => "DATE_FORMAT(Incidents.created, '%a %b %d %Y')",
137
            ),
138
            'year' => array(
139 27
                'label' => 'Last Year',
140 27
                'limit' => date('Y-m-d', strtotime('-1 year')),
141 27
                'group' => "DATE_FORMAT(Incidents.created, '%b %u %Y')",
142
            ),
143
        );
144 27
    }
145
146
    /**
147
     * creates an incident/report record given a raw bug report object.
148
     *
149
     * This gets a decoded bug report from the submitted json body. This has not
150
     * yet been santized. It either adds it as an incident to another report or
151
     * creates a new report if nothing matches.
152
     *
153
     * @param array $bugReport the bug report being submitted
154
     *
155
     * @return array of:
156
     *          1. array of inserted incident ids. If the report/incident was not
157
     *               correctly saved, false is put in it place.
158
     *          2. array of newly created report ids. If no new report was created,
159
     *               an empty array is returned
160
     */
161 2
    public function createIncidentFromBugReport($bugReport)
162
    {
163 2
        if ($bugReport == null) {
164 1
            return array('incidents' => array(false), 'reports' => array());
165
        }
166 2
        $incident_ids = array();    // array to hold ids of all the inserted incidents
167 2
        $new_report_ids = array(); // array to hold ids of all newly created reports
168
169
        // Avoid storing too many errors from single report
170 2
        if (isset($bugReport['errors']) && count($bugReport['errors']) > 20) {
171 1
            $bugReport['errors'] = array_slice($bugReport['errors'], 0, 20);
172
        }
173
174
        // Also sanitizes the bug report
175 2
        $schematizedIncidents = $this->_getSchematizedIncidents($bugReport);
176 2
        $incidentsTable = TableRegistry::get('Incidents');
177 2
        $reportsTable = TableRegistry::get('Reports');
178 2
        foreach ($schematizedIncidents as $index => $si) {
179
180
            // find closest report. If not found, create a new report.
181 2
            $closestReport = $this->_getClosestReport($bugReport, $index);
182 2
            if ($closestReport) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $closestReport of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
183 2
                $si['report_id'] = $closestReport['id'];
184 2
                $si = $incidentsTable->newEntity($si);
185 2
                $si->created = date('Y-m-d H:i:s', time());
0 ignored issues
show
Bug introduced by
Accessing created on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
186 2
                $si->modified = date('Y-m-d H:i:s', time());
0 ignored issues
show
Bug introduced by
Accessing modified on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
187
188 2
                $this->_logLongIncidentSubmissions($si, $incident_ids);
189 2
                if (in_array(false, $incident_ids)) {
190 2
                    break;
191
                }
192
            } else {
193
                // no close report. Create a new report.
194 2
                $report = $this->_getReportDetails($bugReport, $index);
195
196 2
                $this->_logLongIncidentSubmissions($si, $incident_ids);
197 2
                if (in_array(false, $incident_ids)) {
198
                    break;
199
                }
200
201 2
                $report = $reportsTable->newEntity($report);
202 2
                $report->created = date('Y-m-d H:i:s', time());
0 ignored issues
show
Bug introduced by
Accessing created on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
203 2
                $report->modified = date('Y-m-d H:i:s', time());
0 ignored issues
show
Bug introduced by
Accessing modified on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
204 2
                $reportsTable->save($report);
205
206 2
                $si['report_id'] = $report->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
207 2
                $new_report_ids[] = $report->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
208 2
                $si = $incidentsTable->newEntity($si);
209 2
                $si->created = date('Y-m-d H:i:s', time());
0 ignored issues
show
Bug introduced by
Accessing created on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
210 2
                $si->modified = date('Y-m-d H:i:s', time());
0 ignored issues
show
Bug introduced by
Accessing modified on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
211
            }
212
213 2
            $isSaved = $incidentsTable->save($si);
214 2
            if ($isSaved) {
215 2
                array_push($incident_ids, $si->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
216 2
                if (!$closestReport) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $closestReport of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
217
                    // add notifications entry
218 2
                    $tmpIncident = $incidentsTable->findById($si->id)->all()->first();
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
219 2
                    if (!TableRegistry::get('Notifications')->addNotifications(intval($tmpIncident['report_id']))) {
220
                        Log::write(
221
                            'error',
222
                            'ERRORED: Notification::addNotifications() failed on Report#'
223
                                . $tmpIncident['report_id'],
224 2
                            'alert'
225
                        );
226
                    }
227
                }
228
            } else {
229 2
                array_push($incident_ids, false);
230
            }
231
        }
232
233
        return array(
234 2
            'incidents' => $incident_ids,
235 2
            'reports' => $new_report_ids
236
        );
237
    }
238
239
    /**
240
     * retrieves the closest report to a given bug report.
241
     *
242
     * it checks for another report with the same line number, filename and
243
     * pma_version
244
     *
245
     * @param array $bugReport the bug report being checked
246
     *                         Integer $index: for php exception type
247
     * @param mixed $index
248
     *
249
     * @return array the first similar report or null
250
     */
251 3
    protected function _getClosestReport($bugReport, $index = 0)
252
    {
253 3
        if (isset($bugReport['exception_type'])
254 3
            && $bugReport['exception_type'] == 'php'
255
        ) {
256 2
            $location = $bugReport['errors'][$index]['file'];
257 2
            $linenumber = $bugReport['errors'][$index]['lineNum'];
258
        } else {
259
            list($location, $linenumber) =
260 3
                    $this->_getIdentifyingLocation($bugReport['exception']['stack']);
261
        }
262 3
        $report = TableRegistry::get('Reports')->findByLocationAndLinenumberAndPmaVersion(
263 3
                    $location, $linenumber,
264 3
                    $this->getStrippedPmaVersion($bugReport['pma_version'])
265 3
                )->all()->first();
266
267 3
        return $report;
268
    }
269
270
    /**
271
     * creates the report data from an incident that has no related report.
272
     *
273
     * @param array $bugReport the bug report the report record is being created for
274
     *                         Integer $index: for php exception type
275
     * @param mixed $index
276
     *
277
     * @return array an array with the report fields can be used with Report->save
278
     */
279 3
    protected function _getReportDetails($bugReport, $index = 0)
280
    {
281 3
        if (isset($bugReport['exception_type'])
282 3
            && $bugReport['exception_type'] == 'php'
283
        ) {
284 3
            $location = $bugReport['errors'][$index]['file'];
285 3
            $linenumber = $bugReport['errors'][$index]['lineNum'];
286
            $reportDetails = array(
287 3
                    'error_message' => $bugReport['errors'][$index]['msg'],
288 3
                    'error_name' => $bugReport['errors'][$index]['type'],
289
                    );
290 3
            $exception_type = 1;
291
        } else {
292
            list($location, $linenumber) =
293 3
                $this->_getIdentifyingLocation($bugReport['exception']['stack']);
294
295
            $reportDetails = array(
296 3
                    'error_message' => $bugReport['exception']['message'],
297 3
                    'error_name' => $bugReport['exception']['name'],
298
                    );
299 3
            $exception_type = 0;
300
        }
301
302 3
        $reportDetails = array_merge(
303 3
            $reportDetails,
304
            array(
305 3
                'status' => 'new',
306 3
                'location' => $location,
307 3
                'linenumber' => is_null($linenumber) ? 0 : $linenumber,
308 3
                'pma_version' => $bugReport['pma_version'],
309 3
                'exception_type' => $exception_type,
310
            )
311
        );
312
313 3
        return $reportDetails;
314
    }
315
316
    /**
317
     * creates the incident data from the submitted bug report.
318
     *
319
     * @param array $bugReport the bug report the report record is being created for
320
     *
321
     * @return array an array of schematized incident.
322
     *               Can be used with Incident->save
323
     */
324 3
    protected function _getSchematizedIncidents($bugReport)
325
    {
326
        //$bugReport = Sanitize::clean($bugReport, array('escape' => false));
327 3
        $schematizedReports = array();
328
        $schematizedCommonReport = array(
329 3
            'pma_version' => $this->getStrippedPmaVersion($bugReport['pma_version']),
330 3
            'php_version' => $this->_getSimpleVersion($bugReport['php_version'], 2),
331 3
            'browser' => $bugReport['browser_name'] . ' '
332 3
                    . $this->_getSimpleVersion($bugReport['browser_version'], 1),
333 3
            'user_os' => $bugReport['user_os'],
334 3
            'locale' => $bugReport['locale'],
335 3
            'configuration_storage' => $bugReport['configuration_storage'],
336 3
            'server_software' => $this->_getServer($bugReport['server_software']),
337 3
            'full_report' => json_encode($bugReport),
338
        );
339
340 3
        if (isset($bugReport['exception_type'])
341 3
            && $bugReport['exception_type'] == 'php'
342
        ) {
343
            // for each "errors"
344 3
            foreach ($bugReport['errors'] as $error) {
345 3
                $tmpReport = array_merge(
346 3
                    $schematizedCommonReport,
347
                    array(
348 3
                        'error_name' => $error['type'],
349 3
                        'error_message' => $error['msg'],
350 3
                        'script_name' => $error['file'],
351 3
                        'stacktrace' => json_encode($error['stackTrace']),
352 3
                        'stackhash' => $error['stackhash'],
353 3
                        'exception_type' => 1,         // 'php'
354
                    )
355
                );
356 3
                array_push($schematizedReports, $tmpReport);
357
            }
358
        } else {
359 3
            $tmpReport = array_merge(
360 3
                $schematizedCommonReport,
361
                array(
362 3
                    'error_name' => $bugReport['exception']['name'],
363 3
                    'error_message' => $bugReport['exception']['message'],
364 3
                    'script_name' => $bugReport['script_name'],
365 3
                    'stacktrace' => json_encode($bugReport['exception']['stack']),
366 3
                    'stackhash' => $this->getStackHash($bugReport['exception']['stack']),
367 3
                    'exception_type' => 0,     //'js'
368
                )
369
            );
370
371 3
            if (isset($bugReport['steps'])) {
372 3
                $tmpReport['steps'] = $bugReport['steps'];
373
            }
374 3
            array_push($schematizedReports, $tmpReport);
375
        }
376
377 3
        return $schematizedReports;
378
    }
379
380
    /**
381
     * Gets the identifiying location info from a stacktrace.
382
     *
383
     * This is used to skip stacktrace levels that are within the error reporting js
384
     * files that sometimes appear in the stacktrace but are not related to the bug
385
     * report
386
     *
387
     * returns two things in an array:
388
     * - the first element is the filename/scriptname of the error
389
     * - the second element is the linenumber of the error
390
     *
391
     * @param array $stacktrace the stacktrace being examined
392
     *
393
     * @return array an array with the filename/scriptname and linenumber of the
394
     *               error
395
     */
396 5
    protected function _getIdentifyingLocation($stacktrace)
397
    {
398 5
        $fallback = array('UNKNOWN', 0);
399 5
        foreach ($stacktrace as $level) {
400 5
            if (isset($level['filename'])) {
401
                // ignore unrelated files that sometimes appear in the error report
402 5
                if ($level['filename'] === 'tracekit/tracekit.js') {
403 1
                    continue;
404 5
                } elseif ($level['filename'] === 'error_report.js') {
405
                    // in case the error really is in the error_report.js file save it for
406
                    // later
407 1
                    if ($fallback[0] == 'UNKNOWN') {
408 1
                        $fallback = array($level['filename'], $level['line']);
409
                    }
410 1
                    continue;
411
                }
412
413 5
                return array($level['filename'], $level['line']);
414 1
            } elseif (isset($level['scriptname'])) {
415 1
                return array($level['scriptname'], $level['line']);
416
            }
417 1
            continue;
418
        }
419
420 1
        return $fallback;
421
    }
422
423
    /**
424
     * Gets a part of a version string according to the specified version Length.
425
     *
426
     * @param string $versionString the version string
427
     * @param string $versionLength the number of version components to return. eg
428
     *                              1 for major version only and 2 for major and
429
     *                              minor version
430
     *
431
     * @return string the major and minor version part
432
     */
433 4
    protected function _getSimpleVersion($versionString, $versionLength)
434
    {
435 4
        $versionLength = (int) $versionLength;
436 4
        if ($versionLength < 1) {
437 1
            $versionLength = 1;
438
        }
439
        /* modify the re to accept a variable number of version components. I
440
         * atleast take one component and optionally get more components if need be.
441
         * previous code makes sure that the $versionLength variable is a positive
442
         * int
443
         */
444 4
        $result = preg_match(
445 4
            "/^(\d+\.){" . ($versionLength - 1) . "}\d+/",
446 4
            $versionString,
447 4
            $matches
448
        );
449 4
        if ($result) {
450 4
            $simpleVersion = $matches[0];
451
452 4
            return $simpleVersion;
453
        }
454
455
        return $versionString;
456
    }
457
458
    /**
459
     * Returns the version string stripped of
460
     * 'deb', 'ubuntu' and other suffixes
461
     *
462
     * @param string $versionString phpMyAdmin version
463
     *
464
     * @return string stripped phpMyAdmin version
465
     */
466 10
    public function getStrippedPmaVersion($versionString)
467
    {
468 10
        $allowedRegexp = '/^(\d+)(\.\d+){0,3}(\-.*)?/';
469 10
        $matches = array();
470
471
        // Check if $versionString matches the regexp
472
        // and store the matched strings
473 10
        if (preg_match($allowedRegexp, $versionString, $matches)) {
474 10
            return $matches[0];
475
        }
476
477
        // If $versionString does not match the regexp at all,
478
        // leave it as it is
479
        return $versionString;
480
    }
481
482
    /**
483
     * Gets the server name and version from the server signature.
484
     *
485
     * @param string $signature the server signature
486
     *
487
     * @return string the server name and version or UNKNOWN
488
     */
489 4
    protected function _getServer($signature)
490
    {
491 4
        if (preg_match("/(apache\/\d+\.\d+)|(nginx\/\d+\.\d+)|(iis\/\d+\.\d+)"
492 4
                . "|(lighttpd\/\d+\.\d+)/i",
493 4
                $signature, $matches)) {
494 4
            return $matches[0];
495
        }
496
497 1
        return 'UNKNOWN';
498
    }
499
500
    /**
501
     * returns the hash pertaining to a stacktrace.
502
     *
503
     * @param array $stacktrace the stacktrace in question
504
     *
505
     * @return string the hash string of the stacktrace
506
     */
507 4
    public function getStackHash($stacktrace)
508
    {
509 4
        $handle = hash_init('md5');
510 4
        foreach ($stacktrace as $level) {
511 4
            $elements = array('filename', 'scriptname', 'line', 'func', 'column');
512 4
            foreach ($elements as $element) {
513 4
                if (!isset($level[$element])) {
514 4
                    continue;
515
                }
516 4
                hash_update($handle, $level[$element]);
517
            }
518
        }
519
520 4
        return hash_final($handle);
521
    }
522
523
    /**
524
     * Checks the length of stacktrace and full_report
525
     * and logs if it is greater than what it can hold
526
     *
527
     * @param array $si           submitted incident
528
     * @param array $incident_ids incident IDs
529
     *
530
     * @return array $incident_ids
531
     */
532 2
    private function _logLongIncidentSubmissions($si, &$incident_ids) {
533
534 2
        $stacktraceLength = mb_strlen($si['stacktrace']);
535 2
        $fullReportLength = mb_strlen($si['full_report']);
536 2
        $errorMessageLength = mb_strlen($si['error_message']);
537
538 2
        if ($stacktraceLength > 65535
539 2
            || $fullReportLength > 65535
540 2
            || $errorMessageLength > 200 // length of field in 'incidents' table
541
        ) {
542
            // If length of report is longer than
543
            // what can fit in the table field,
544
            // we log it and don't save it in the database
545
            Log::error(
546
                'Too long data submitted in the incident. The length of stacktrace: '
547
                . $stacktraceLength . ', the length of bug report: '
548
                . $fullReportLength . ', the length of error message: '
549
                . $errorMessageLength . '. The full incident reported was as follows: '
550
                . json_encode($si)
551
            );
552
553
            // add a 'false' to the return array
554
            array_push($incident_ids, false);
555
        }
556 2
    }
557
}
558