1
|
|
|
<?php |
2
|
|
|
namespace Crossjoin\Browscap\Parser\Sqlite; |
3
|
|
|
|
4
|
|
|
use Crossjoin\Browscap\Exception\InvalidArgumentException; |
5
|
|
|
use Crossjoin\Browscap\Exception\ParserConditionNotSatisfiedException; |
6
|
|
|
use Crossjoin\Browscap\Exception\ParserRuntimeException; |
7
|
|
|
use Crossjoin\Browscap\Exception\UnexpectedValueException; |
8
|
|
|
use Crossjoin\Browscap\Parser\ReaderInterface; |
9
|
|
|
use Crossjoin\Browscap\Parser\Sqlite\Adapter\AdapterFactory; |
10
|
|
|
use Crossjoin\Browscap\Parser\Sqlite\Adapter\AdapterInterface; |
11
|
|
|
use Crossjoin\Browscap\PropertyFilter\PropertyFilterTrait; |
12
|
|
|
use Crossjoin\Browscap\Source\Ini\GetRegExpForPatternTrait; |
13
|
|
|
use Crossjoin\Browscap\Type; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Class Reader |
17
|
|
|
* |
18
|
|
|
* @package Crossjoin\Browscap\Parser\Sqlite |
19
|
|
|
* @author Christoph Ziegenberg <[email protected]> |
20
|
|
|
* @link https://github.com/crossjoin/browscap |
21
|
|
|
*/ |
22
|
|
|
class Reader implements ReaderInterface |
23
|
|
|
{ |
24
|
|
|
use DataDirectoryTrait; |
25
|
|
|
use DataVersionHashTrait; |
26
|
|
|
use PropertyFilterTrait; |
27
|
|
|
use GetRegExpForPatternTrait; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var AdapterInterface |
31
|
|
|
*/ |
32
|
|
|
protected $adapter; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var string |
36
|
|
|
*/ |
37
|
|
|
protected $databaseFile; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var int |
41
|
|
|
*/ |
42
|
|
|
protected $browserId; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var int |
46
|
|
|
*/ |
47
|
|
|
protected $browserParentId; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var array |
51
|
|
|
*/ |
52
|
|
|
protected $browserPatternKeywords; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var string |
56
|
|
|
*/ |
57
|
|
|
protected $sqliteVersion; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Writer constructor. |
61
|
|
|
* |
62
|
|
|
* @param string $dataDirectory |
63
|
|
|
* |
64
|
|
|
* @throws InvalidArgumentException |
65
|
|
|
* @throws ParserConditionNotSatisfiedException |
66
|
|
|
*/ |
67
|
|
|
public function __construct($dataDirectory) |
68
|
|
|
{ |
69
|
|
|
if (!is_string($dataDirectory)) { |
70
|
|
|
throw new InvalidArgumentException( |
71
|
|
|
"Invalid type '" . gettype($dataDirectory) . "' for argument 'dataDirectory'." |
72
|
|
|
); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
$this->setDataDirectory($dataDirectory); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @inheritdoc |
80
|
|
|
* |
81
|
|
|
* @throws InvalidArgumentException |
82
|
|
|
* @throws ParserConditionNotSatisfiedException |
83
|
|
|
* @throws ParserRuntimeException |
84
|
|
|
* @throws UnexpectedValueException |
85
|
|
|
*/ |
86
|
|
View Code Duplication |
protected function getAdapter() |
|
|
|
|
87
|
|
|
{ |
88
|
|
|
if ($this->adapter === null) { |
89
|
|
|
$databaseFile = $this->getDatabasePath(); |
90
|
|
|
$adapter = AdapterFactory::getInstance($databaseFile); |
91
|
|
|
$this->setAdapter($adapter); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
return $this->adapter; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @return string |
99
|
|
|
* |
100
|
|
|
* @throws InvalidArgumentException |
101
|
|
|
* @throws ParserRuntimeException |
102
|
|
|
*/ |
103
|
|
|
protected function getDatabasePath() |
104
|
|
|
{ |
105
|
|
|
$databasePath = $this->getDataDirectory() . DIRECTORY_SEPARATOR . $this->getDatabaseFileName(); |
106
|
|
|
|
107
|
|
|
if (!$this->isFileReadable($databasePath)) { |
108
|
|
|
if (!file_exists($databasePath)) { |
109
|
|
|
throw new ParserRuntimeException( |
110
|
|
|
"Linked database file '$databasePath' not found. Parser data need to be generated.", |
|
|
|
|
111
|
|
|
1458898365 |
112
|
|
|
); |
113
|
|
|
} else { |
114
|
|
|
throw new ParserRuntimeException("Linked database file '$databasePath' is not readable.", 1458898366); |
|
|
|
|
115
|
|
|
} |
116
|
|
|
} elseif (!is_file($databasePath)) { |
117
|
|
|
throw new ParserRuntimeException("Invalid database file name '$databasePath' in link file.", 1458898367); |
|
|
|
|
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
return $databasePath; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @return string |
125
|
|
|
* |
126
|
|
|
* @throws InvalidArgumentException |
127
|
|
|
* @throws ParserRuntimeException |
128
|
|
|
*/ |
129
|
|
|
protected function getDatabaseFileName() |
130
|
|
|
{ |
131
|
|
|
// Get database to use, saved in the link file (as symlinks are not available or only |
132
|
|
|
// with admin permissions on Windows). |
133
|
|
|
$linkFile = $this->getDataDirectory() . DIRECTORY_SEPARATOR . Parser::LINK_FILENAME; |
134
|
|
|
if ($this->isFileReadable($linkFile)) { |
135
|
|
|
return (string)file_get_contents($linkFile); |
136
|
|
|
} elseif (!file_exists($linkFile)) { |
137
|
|
|
throw new ParserRuntimeException( |
138
|
|
|
"Database link file '$linkFile' not found. Parser data need to be generated.", |
|
|
|
|
139
|
|
|
1458898368 |
140
|
|
|
); |
141
|
|
|
} else { |
142
|
|
|
throw new ParserRuntimeException("Database link file '$linkFile' is not readable.", 1458898369); |
|
|
|
|
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* @param $file |
148
|
|
|
* |
149
|
|
|
* @return bool |
150
|
|
|
* @throws InvalidArgumentException |
151
|
|
|
*/ |
152
|
|
View Code Duplication |
protected function isFileReadable($file) |
|
|
|
|
153
|
|
|
{ |
154
|
|
|
if (!is_string($file)) { |
155
|
|
|
throw new InvalidArgumentException( |
156
|
|
|
"Invalid type '" . gettype($file) . "' for argument 'file'." |
157
|
|
|
); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
return is_readable($file); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* @param AdapterInterface $adapter |
165
|
|
|
*/ |
166
|
|
|
protected function setAdapter(AdapterInterface $adapter) |
167
|
|
|
{ |
168
|
|
|
$this->adapter = $adapter; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* @inheritdoc |
173
|
|
|
* |
174
|
|
|
* @throws InvalidArgumentException |
175
|
|
|
* @throws UnexpectedValueException |
176
|
|
|
*/ |
177
|
|
|
public function isUpdateRequired() |
178
|
|
|
{ |
179
|
|
|
try { |
180
|
|
|
$query = 'SELECT data_hash FROM info LIMIT 1'; |
181
|
|
|
$dataHash = ''; |
182
|
|
|
foreach ($this->getAdapter()->query($query) as $row) { |
183
|
|
|
$dataHash = $row['data_hash']; |
184
|
|
|
} |
185
|
|
|
$return = ($dataHash !== $this->getDataVersionHash()); |
186
|
|
|
} catch (ParserConditionNotSatisfiedException $e) { |
187
|
|
|
$return = true; |
188
|
|
|
} catch (ParserRuntimeException $e) { |
189
|
|
|
$return = true; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
return $return; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* @inheritdoc |
197
|
|
|
* |
198
|
|
|
* @throws InvalidArgumentException |
199
|
|
|
* @throws ParserConditionNotSatisfiedException |
200
|
|
|
* @throws ParserRuntimeException |
201
|
|
|
* @throws UnexpectedValueException |
202
|
|
|
*/ |
203
|
|
|
public function getReleaseTime() |
204
|
|
|
{ |
205
|
|
|
$query = 'SELECT release_time FROM info LIMIT 1'; |
206
|
|
|
|
207
|
|
|
$releaseTime = 0; |
208
|
|
|
foreach ($this->getAdapter()->query($query) as $row) { |
209
|
|
|
$releaseTime = (int)$row['release_time']; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
return $releaseTime; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* @inheritdoc |
217
|
|
|
* |
218
|
|
|
* @throws InvalidArgumentException |
219
|
|
|
* @throws ParserConditionNotSatisfiedException |
220
|
|
|
* @throws ParserRuntimeException |
221
|
|
|
* @throws UnexpectedValueException |
222
|
|
|
*/ |
223
|
|
|
public function getVersion() |
224
|
|
|
{ |
225
|
|
|
$query = 'SELECT version_id FROM info LIMIT 1'; |
226
|
|
|
|
227
|
|
|
$versionId = 0; |
228
|
|
|
foreach ($this->getAdapter()->query($query) as $row) { |
229
|
|
|
$versionId = (int)$row['version_id']; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
return $versionId; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* @inheritdoc |
237
|
|
|
* |
238
|
|
|
* @throws InvalidArgumentException |
239
|
|
|
* @throws ParserConditionNotSatisfiedException |
240
|
|
|
* @throws ParserRuntimeException |
241
|
|
|
* @throws UnexpectedValueException |
242
|
|
|
*/ |
243
|
|
|
public function getType() |
244
|
|
|
{ |
245
|
|
|
$query = 'SELECT type_id FROM info LIMIT 1'; |
246
|
|
|
|
247
|
|
|
$typeId = Type::UNKNOWN; |
248
|
|
|
foreach ($this->getAdapter()->query($query) as $row) { |
249
|
|
|
$typeId = (int)$row['type_id']; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
return $typeId; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* @inheritdoc |
257
|
|
|
* |
258
|
|
|
* @throws InvalidArgumentException |
259
|
|
|
* @throws ParserConditionNotSatisfiedException |
260
|
|
|
* @throws ParserRuntimeException |
261
|
|
|
* @throws UnexpectedValueException |
262
|
|
|
*/ |
263
|
|
|
public function getBrowser($userAgent) |
264
|
|
|
{ |
265
|
|
|
if (!is_string($userAgent)) { |
266
|
|
|
throw new InvalidArgumentException( |
267
|
|
|
"Invalid type '" . gettype($userAgent) . "' for argument 'userAgent'." |
268
|
|
|
); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
// Init variables |
272
|
|
|
$browserId = $this->getBrowserId($userAgent); |
273
|
|
|
$browserParentId = $this->getBrowserParentId($userAgent); |
274
|
|
|
|
275
|
|
|
// Get all settings for the found browser |
276
|
|
|
// |
277
|
|
|
// It's best to use a recursive query here, but some linux distributions like CentOS and RHEL |
278
|
|
|
// use old sqlite library versions (like 3.6.20 or 3.7.17), so that we have to check the |
279
|
|
|
// version here and fall back to multiple single queries in case of older versions. |
280
|
|
|
if (version_compare($this->getSqliteVersion(), '3.8.3', '>=')) { |
281
|
|
|
$query = 'SELECT t3.property_key, t4.property_value FROM ('; |
282
|
|
|
$query .= 'WITH RECURSIVE browser_recursive(browser_id,browser_parent_id) AS ('; |
283
|
|
|
$query .= 'SELECT browser_id, browser_parent_id FROM browser WHERE browser_id = :id'; |
284
|
|
|
$query .= ' UNION ALL '; |
285
|
|
|
$query .= 'SELECT browser.browser_id, browser.browser_parent_id FROM browser, browser_recursive '; |
286
|
|
|
$query .= 'WHERE browser_recursive.browser_parent_id IS NOT NULL '; |
287
|
|
|
$query .= 'AND browser.browser_id = browser_recursive.browser_parent_id'; |
288
|
|
|
$query .= ') '; |
289
|
|
|
$query .= 'SELECT MAX(t2.browser_id) AS browser_id, t2.property_key_id FROM browser_recursive t1 '; |
290
|
|
|
$query .= 'JOIN browser_property t2 ON t2.browser_id = t1.browser_id '; |
291
|
|
|
$query .= 'GROUP BY t2.property_key_id'; |
292
|
|
|
$query .= ') t1 '; |
293
|
|
|
$query .= 'JOIN browser_property t2 ON t2.browser_id = t1.browser_id '; |
294
|
|
|
$query .= 'AND t2.property_key_id = t1.property_key_id '; |
295
|
|
|
$query .= 'JOIN browser_property_key t3 ON t3.property_key_id = t2.property_key_id '; |
296
|
|
|
$query .= 'JOIN browser_property_value t4 ON t4.property_value_id = t2.property_value_id'; |
297
|
|
|
$query .= ' UNION ALL '; |
298
|
|
|
$query .= 'SELECT \'browser_name_pattern\' AS property_key, browser_pattern AS property_value '; |
299
|
|
|
$query .= 'FROM browser WHERE browser_id = :id'; |
300
|
|
|
$query .= ' UNION ALL '; |
301
|
|
|
$query .= 'SELECT \'Parent\' AS property_key, browser_pattern AS property_value '; |
302
|
|
|
$query .= 'FROM browser WHERE browser_id = :parent'; |
303
|
|
|
$statement = $this->getAdapter()->prepare($query); |
304
|
|
|
} else { |
305
|
|
|
$query = 'SELECT browser_id, browser_parent_id FROM browser WHERE browser_id = :id'; |
306
|
|
|
$statement = $this->getAdapter()->prepare($query); |
307
|
|
|
$browserIds = []; |
308
|
|
|
$lastBrowserId = $browserId; |
309
|
|
|
while ($lastBrowserId !== null) { |
310
|
|
|
$result = $statement->execute(['id' => $lastBrowserId]); |
311
|
|
|
|
312
|
|
|
$lastBrowserId = null; |
313
|
|
|
if (count($result) > 0) { |
314
|
|
|
$browserIds[] = (int)$result[0]['browser_id']; |
315
|
|
|
if ($result[0]['browser_parent_id'] !== null) { |
316
|
|
|
$lastBrowserId = (int)$result[0]['browser_parent_id']; |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
$query = 'SELECT t3.property_key, t4.property_value FROM ('; |
322
|
|
|
$query .= 'SELECT MAX(browser_id) AS browser_id, property_key_id '; |
323
|
|
|
$query .= 'FROM browser_property WHERE browser_id IN (' . implode(', ', $browserIds) . ') '; |
324
|
|
|
$query .= 'GROUP BY property_key_id'; |
325
|
|
|
$query .= ') t1 '; |
326
|
|
|
$query .= 'JOIN browser_property t2 ON t2.browser_id = t1.browser_id '; |
327
|
|
|
$query .= 'AND t2.property_key_id = t1.property_key_id '; |
328
|
|
|
$query .= 'JOIN browser_property_key t3 ON t3.property_key_id = t2.property_key_id '; |
329
|
|
|
$query .= 'JOIN browser_property_value t4 ON t4.property_value_id = t2.property_value_id'; |
330
|
|
|
$query .= ' UNION ALL '; |
331
|
|
|
$query .= 'SELECT \'browser_name_pattern\' AS property_key, browser_pattern AS property_value '; |
332
|
|
|
$query .= 'FROM browser WHERE browser_id = :id'; |
333
|
|
|
$query .= ' UNION ALL '; |
334
|
|
|
$query .= 'SELECT \'Parent\' AS property_key, browser_pattern AS property_value '; |
335
|
|
|
$query .= 'FROM browser WHERE browser_id = :parent'; |
336
|
|
|
$statement = $this->getAdapter()->prepare($query); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
$properties = []; |
340
|
|
|
foreach ($statement->execute(['id' => $browserId, 'parent' => $browserParentId]) as $row) { |
341
|
|
|
$properties[$row['property_key']] = $row['property_value']; |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
// Set regular expression properties |
345
|
|
|
$propertyFilter = $this->getPropertyFilter(); |
346
|
|
|
if (!$propertyFilter->isFiltered('browser_name_regex')) { |
347
|
|
|
$properties['browser_name_regex'] = $this->getRegExpForPattern($properties['browser_name_pattern']); |
348
|
|
|
} |
349
|
|
|
if ($propertyFilter->isFiltered('browser_name_pattern')) { |
350
|
|
|
unset($properties['browser_name_pattern']); |
351
|
|
|
} |
352
|
|
|
if ($propertyFilter->isFiltered('Parent')) { |
353
|
|
|
unset($properties['Parent']); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
// IMPORTANT: Reset browserId and browserParentId for next call |
357
|
|
|
$this->browserId = null; |
358
|
|
|
$this->browserParentId = null; |
359
|
|
|
|
360
|
|
|
// The settings are in random order, so sort them |
361
|
|
|
return $this->sortProperties($properties); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* @return string |
366
|
|
|
* @throws \Crossjoin\Browscap\Exception\UnexpectedValueException |
367
|
|
|
* @throws \Crossjoin\Browscap\Exception\ParserConditionNotSatisfiedException |
368
|
|
|
*/ |
369
|
|
|
protected function getSqliteVersion() |
370
|
|
|
{ |
371
|
|
|
if ($this->sqliteVersion === null) { |
372
|
|
|
|
373
|
|
|
$this->sqliteVersion = '0.0.0'; |
374
|
|
|
|
375
|
|
|
// Try to get the version number |
376
|
|
|
$query = 'SELECT sqlite_version() AS version'; |
377
|
|
|
try { |
378
|
|
|
$result = $this->getAdapter()->query($query); |
379
|
|
|
if (count($result) > 0) { |
380
|
|
|
$this->sqliteVersion = $result[0]['version']; |
381
|
|
|
} |
382
|
|
|
} catch (ParserRuntimeException $exception) { |
|
|
|
|
383
|
|
|
} |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
return $this->sqliteVersion; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* @param string $userAgent |
391
|
|
|
* |
392
|
|
|
* @return int |
393
|
|
|
* @throws InvalidArgumentException |
394
|
|
|
* @throws ParserConditionNotSatisfiedException |
395
|
|
|
* @throws ParserRuntimeException |
396
|
|
|
* @throws UnexpectedValueException |
397
|
|
|
*/ |
398
|
|
|
protected function getBrowserId($userAgent) |
399
|
|
|
{ |
400
|
|
|
if ($this->browserId === null) { |
401
|
|
|
$this->findBrowser($userAgent); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
return $this->browserId; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* @param string $userAgent |
409
|
|
|
* |
410
|
|
|
* @return int |
411
|
|
|
* @throws InvalidArgumentException |
412
|
|
|
* @throws ParserConditionNotSatisfiedException |
413
|
|
|
* @throws ParserRuntimeException |
414
|
|
|
* @throws UnexpectedValueException |
415
|
|
|
*/ |
416
|
|
|
protected function getBrowserParentId($userAgent) |
417
|
|
|
{ |
418
|
|
|
if ($this->browserParentId === null) { |
419
|
|
|
$this->findBrowser($userAgent); |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
return $this->browserParentId; |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* @param string $userAgent |
427
|
|
|
* |
428
|
|
|
* @throws InvalidArgumentException |
429
|
|
|
* @throws ParserConditionNotSatisfiedException |
430
|
|
|
* @throws ParserRuntimeException |
431
|
|
|
* @throws UnexpectedValueException |
432
|
|
|
*/ |
433
|
|
|
protected function findBrowser($userAgent) |
434
|
|
|
{ |
435
|
|
|
if (!is_string($userAgent)) { |
436
|
|
|
throw new InvalidArgumentException( |
437
|
|
|
"Invalid type '" . gettype($userAgent) . "' for argument 'userAgent'." |
438
|
|
|
); |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
// Check each keyword table for the browser pattern |
442
|
|
|
$this->findBrowserInKeywordTables($userAgent); |
443
|
|
|
|
444
|
|
|
// If no match found in keyword tables, check the default table |
445
|
|
|
// (this also includes the '*' pattern for the default match). |
446
|
|
|
if ($this->browserId === null) { |
447
|
|
|
$this->findBrowserInDefaultTable($userAgent); |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
// Check if data found (the last step should always find the default settings) |
451
|
|
|
if ($this->browserId === null) { |
452
|
|
|
throw new ParserRuntimeException( |
453
|
|
|
"No result found for user agent '$userAgent'. There seems to be something wrong with the data." |
|
|
|
|
454
|
|
|
); |
455
|
|
|
} |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* @param string $userAgent |
460
|
|
|
* @throws InvalidArgumentException |
461
|
|
|
* @throws ParserConditionNotSatisfiedException |
462
|
|
|
* @throws ParserRuntimeException |
463
|
|
|
* @throws UnexpectedValueException |
464
|
|
|
*/ |
465
|
|
|
protected function findBrowserInKeywordTables($userAgent) |
466
|
|
|
{ |
467
|
|
|
if (!is_string($userAgent)) { |
468
|
|
|
throw new InvalidArgumentException( |
469
|
|
|
"Invalid type '" . gettype($userAgent) . "' for argument 'userAgent'." |
470
|
|
|
); |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
$query = 'SELECT browser_id, browser_pattern_length FROM "search_[keyword]" '; |
474
|
|
|
$query .= 'WHERE browser_pattern_length >= :length AND :agent GLOB browser_pattern'; |
475
|
|
|
|
476
|
|
|
$userAgentLowered = strtolower($userAgent); |
477
|
|
|
$maxLength = 0; |
478
|
|
|
$browserId = null; |
479
|
|
|
foreach ($this->getPatternKeywords() as $patternKeyword) { |
480
|
|
|
if (strpos($userAgentLowered, $patternKeyword) !== false) { |
481
|
|
|
$statement = $this->getAdapter()->prepare(str_replace('[keyword]', $patternKeyword, $query)); |
482
|
|
|
|
483
|
|
|
/** @noinspection PdoApiUsageInspection */ |
484
|
|
|
foreach ($statement->execute(['length' => $maxLength, 'agent' => $userAgentLowered]) as $row) { |
485
|
|
|
$tmpLength = (int)$row['browser_pattern_length']; |
486
|
|
|
$tmpBrowserId = (int)$row['browser_id']; |
487
|
|
|
|
488
|
|
|
if ($tmpLength < $maxLength) { |
489
|
|
|
continue; // @codeCoverageIgnore |
490
|
|
|
} |
491
|
|
|
if ($tmpLength === $maxLength && $browserId !== null && $tmpBrowserId < $browserId) { |
492
|
|
|
continue; // @codeCoverageIgnore |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
$browserId = (int)$row['browser_id']; |
496
|
|
|
$maxLength = $tmpLength; |
497
|
|
|
} |
498
|
|
|
} |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
if ($browserId !== null) { |
502
|
|
|
$this->browserId = $browserId; |
503
|
|
|
|
504
|
|
|
$query = 'SELECT browser_parent_id FROM browser WHERE browser_id = :id'; |
505
|
|
|
$statement = $this->getAdapter()->prepare($query); |
506
|
|
|
/** @noinspection PdoApiUsageInspection */ |
507
|
|
|
foreach ($statement->execute(['id' => $browserId]) as $row) { |
508
|
|
|
$this->browserParentId = (int)$row['browser_parent_id']; |
509
|
|
|
} |
510
|
|
|
} |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* @param string $userAgent |
515
|
|
|
* |
516
|
|
|
* @throws InvalidArgumentException |
517
|
|
|
* @throws ParserConditionNotSatisfiedException |
518
|
|
|
* @throws ParserRuntimeException |
519
|
|
|
* @throws UnexpectedValueException |
520
|
|
|
*/ |
521
|
|
|
protected function findBrowserInDefaultTable($userAgent) |
522
|
|
|
{ |
523
|
|
|
if (!is_string($userAgent)) { |
524
|
|
|
throw new InvalidArgumentException( |
525
|
|
|
"Invalid type '" . gettype($userAgent) . "' for argument 'userAgent'." |
526
|
|
|
); |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
// Build query with default table |
530
|
|
|
$query = 'SELECT t2.browser_id, t2.browser_parent_id FROM '; |
531
|
|
|
$query .= '(SELECT MIN(browser_id) AS browser_id FROM search WHERE :agent GLOB browser_pattern) t1 '; |
532
|
|
|
$query .= 'JOIN browser t2 ON t2.browser_id = t1.browser_id'; |
533
|
|
|
$statement = $this->getAdapter()->prepare($query); |
534
|
|
|
|
535
|
|
|
/** @noinspection PdoApiUsageInspection */ |
536
|
|
|
foreach ($statement->execute(['agent' => strtolower($userAgent)]) as $row) { |
537
|
|
|
$this->browserId = (int)$row['browser_id']; |
538
|
|
|
$this->browserParentId = (int)$row['browser_parent_id']; |
539
|
|
|
} |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* @return array |
544
|
|
|
* @throws InvalidArgumentException |
545
|
|
|
* @throws ParserConditionNotSatisfiedException |
546
|
|
|
* @throws ParserRuntimeException |
547
|
|
|
* @throws UnexpectedValueException |
548
|
|
|
*/ |
549
|
|
|
protected function getPatternKeywords() |
550
|
|
|
{ |
551
|
|
|
if ($this->browserPatternKeywords === null) { |
552
|
|
|
$this->browserPatternKeywords = []; |
553
|
|
|
$query = 'SELECT keyword_value FROM keyword ORDER BY keyword_id'; |
554
|
|
|
foreach ($this->getAdapter()->query($query) as $row) { |
555
|
|
|
$this->browserPatternKeywords[] = $row['keyword_value']; |
556
|
|
|
} |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
return $this->browserPatternKeywords; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* @param array $properties |
564
|
|
|
* |
565
|
|
|
* @return array |
566
|
|
|
*/ |
567
|
|
|
protected function sortProperties(array $properties) |
568
|
|
|
{ |
569
|
|
|
ksort($properties); |
570
|
|
|
|
571
|
|
|
return $properties; |
572
|
|
|
} |
573
|
|
|
} |
574
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.