Completed
Push — master ( 9ca888...b02861 )
by Korvin
02:45
created

Browserslist::rejectQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 3
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
crap 2
1
<?php
2
3
namespace Buttress\Browserslist;
4
5
use Buttress\Browserslist\Browser\VersionNormalizer;
6
use Buttress\Browserslist\Query\CustomStats;
7
use Buttress\Browserslist\Query\DirectDriver;
8
use Buttress\Browserslist\Query\FirefoxESR;
9
use Buttress\Browserslist\Query\GlobalStats;
10
use Buttress\Browserslist\Query\LastByBrowser;
11
use Buttress\Browserslist\Query\LastVersions;
12
use Buttress\Browserslist\Query\OperaMini;
13
use Buttress\Browserslist\Query\Range;
14
use Buttress\Browserslist\Query\Versions;
15
use Illuminate\Support\Collection;
16
17
class Browserslist
18
{
19
    /** @var array This array may contain fqn classname strings or instances of \Buttress\BrowsersList\Query\Driver */
20
    protected $queryDrivers;
21
22
    /** @var \Illuminate\Support\Collection */
23
    protected $aliases;
24
25
    /** @var Collection */
26
    protected $versionAliases;
27
28
    /** @var Collection */
29
    protected $usage;
30
31
    /** @var \Illuminate\Support\Collection */
32
    protected $browsers;
33
34
    /** @var \Illuminate\Support\Collection */
35
    protected $data;
36
37
    /** @var array|string */
38
    protected $defaultQuery;
39
40
    /** @var VersionNormalizer */
41
    protected $normalizer;
42
43 9
    public function __construct($data)
44
    {
45 9
        if (!$data instanceof Collection) {
46 9
            $data = new Collection($data);
47
        }
48
49 9
        $this->defaultQuery = '> 1%, last 2 versions, Firefox ESR';
50 9
        $this->data = $data;
51 9
        $this->versionAliases = new Collection();
52
53 9
        $this->aliases = new Collection([
54 9
            'fx' => 'firefox',
55
            'ff' => 'firefox',
56
            'ios' => 'ios_saf',
57
            'explorer' => 'ie',
58
            'blackberry' => 'bb',
59
            'explorermobile' => 'ie_mob',
60
            'operamini' => 'op_mini',
61
            'operamobile' => 'op_mob',
62
            'chromeandroid' => 'and_chr',
63
            'firefoxandroid' => 'and_ff',
64
            'ucandroid' => 'and_uc'
65
        ]);
66
67 9
        $this->browsers = new Collection(['safari', 'opera', 'ios_saf', 'ie_mob', 'ie', 'edge', 'firefox', 'chrome']);
68 9
        $this->queryDrivers = [
69
            LastVersions::class,
70
            LastByBrowser::class,
71
            OperaMini::class,
72
            CustomStats::class,
73
            GlobalStats::class,
74
            FirefoxESR::class,
75
            Versions::class,
76
            Range::class,
77
            DirectDriver::class
78
        ];
79 9
    }
80
81
    /**
82
     * Get the usage of a browser or set of browsers
83
     * @param array $browsers
84
     * @param string $country
85
     * @return mixed
86
     */
87
    public function coverage(array $browsers, $country = 'global')
88
    {
89
        $list = $this;
90
91
        if (strtolower($country) !== 'global') {
92
            throw new \InvalidArgumentException('Country specific coverage is not supported.');
93
        }
94
95
        return Collection::make($browsers)->reduce(function ($total, $browser) use ($list) {
96
            if (!$usage = array_get($list->getUsage()->get("global"), $browser)) {
97
                $usage = array_get($list->getUsage()->get("global"), preg_replace('/ [\d.]+$/', ' 0', $browser));
98
            }
99
100
            return $total + floatval($usage);
101
        }, 0);
102
    }
103
104
    /**
105
     * Get the data for a particular browser name
106
     * @param $name
107
     * @return array|null
108
     */
109 9
    public function getDataByBrowser($name)
110
    {
111 9
        $name = strtolower($name);
112 9
        $data = $this->getData();
113 9
        $aliases = $this->getAliases();
114
115 9
        if ($aliases->has($name)) {
116 1
            $name = $aliases[$name];
117
        }
118
119
        $browser = $data->first(function ($browser) use ($name) {
120 9
            return $browser['name'] === $name;
121 9
        });
122
123 9
        if ($browser) {
124 8
            return $browser;
125
        }
126 5
    }
127
128
    /**
129
     * Get an instance of the version normalizer
130
     * @return \Buttress\Browserslist\Browser\VersionNormalizer
131
     */
132 1
    public function getVersionNormalizer()
133
    {
134 1
        if (!$this->normalizer) {
135 1
            $this->normalizer = new VersionNormalizer($this);
136
        }
137
138 1
        return $this->normalizer;
139
    }
140
141
    /**
142
     * Run a query against the Browserlist data
143
     * @param $query
144
     * @return \Illuminate\Support\Collection
145
     * @throws \InvalidArgumentException If a query passed is not recognized
146
     */
147 9
    public function query($query = null)
148
    {
149 9
        if ($query === null) {
150
            $query = $this->defaultQuery;
151
        }
152
153 9
        $result = new Collection();
154 9
        foreach ($this->generateQueries($query) as $subQuery) {
155 9
            if ($rejectQuery = $this->shouldReject($subQuery)) {
156
                $result = $this->rejectQuery($result, $rejectQuery);
157
            } else {
158 9
                $result = $result->merge($this->handleQuery($subQuery));
159
            }
160
        }
161
162 7
        return $result->unique()->sort($this->queryResultSort())->values();
163
    }
164
165
    /**
166
     * Generator method for retrieving cleaned query strings
167
     * @param $query
168
     * @return \Generator
169
     */
170 9
    private function generateQueries($query)
171
    {
172 9
        if (is_string($query)) {
173 9
            $query = explode(',', $query);
174
        }
175
176 9
        foreach ((array)$query as $queryString) {
177 9
            if ($trimmed = trim($queryString)) {
178 9
                yield $trimmed;
179
            }
180
        }
181 7
    }
182
183
    /**
184
     * @param $query
185
     * @return bool|string
186
     */
187 9
    private function shouldReject($query)
188
    {
189 9
        if (strlen($query) > 4 && substr($query, 0, 4) == 'not ') {
190
            return substr($query, 4);
191
        }
192
193 9
        return false;
194
    }
195
196
    /**
197
     * Takes a collection and removes the results of the passed query
198
     * @param \Illuminate\Support\Collection $result
199
     * @param $query
200
     * @return Collection
201
     */
202
    private function rejectQuery(Collection $result, $query)
203
    {
204
        $without = $this->handleQuery($query);
205
206
        return $result->reject(function ($item) use ($without) {
207
            return !!$without->contains($item);
208
        });
209
    }
210
211
    /**
212
     * Internal query handling method, this delegates to drivers
213
     * @param $query
214
     * @return \Illuminate\Support\Collection
215
     */
216 9
    private function handleQuery($query)
217
    {
218 9
        foreach ($this->queryDriverGenerator() as $driver) {
219 9
            if ($driver->handlesQuery($query)) {
220 9
                return $driver->query($query, $this);
221
            }
222
        }
223
224
        throw new \InvalidArgumentException("Unknown browser query `{$query}`");
225
    }
226
227
    /**
228
     * Inflates drivers as they are iterated over
229
     * @return \Generator
230
     */
231 9
    private function queryDriverGenerator()
232
    {
233 9
        foreach ($this->queryDrivers as $key => $driver) {
234 9
            if (is_string($driver) && class_exists($driver)) {
235 9
                $driver = new $driver;
236 9
                $this->queryDrivers[$key] = $driver;
237 9
                yield $driver;
238
            } else {
239 5
                yield $driver;
240
            }
241
        }
242
    }
243
244
    /**
245
     * Result sort closure
246
     *
247
     * Sorts in natural, then reverse natural order.
248
     *
249
     * Example sort order:
250
     * a 10
251
     * a 9
252
     * b 100
253
     * b 60
254
     * z 50
255
     * z 1
256
     *
257
     * @return \Closure
258
     */
259
    private function queryResultSort()
260
    {
261 7
        return function ($a, $b) {
262 5
            $result = strnatcmp($a, $b);
263
264 5
            $lettersOnlyA = preg_replace('/[^a-z]/i', '', $a);
265 5
            $lettersOnlyB = preg_replace('/[^a-z]/i', '', $b);
266
267
            // If the browsers are the same
268 5
            if ($lettersOnlyA === $lettersOnlyB) {
269 4
                return -1 * $result;
270
            }
271
272 3
            return $result;
273 7
        };
274
    }
275
276
    /**
277
     * @return array|string
278
     */
279
    public function getDefaultQuery()
280
    {
281
        return $this->defaultQuery;
282
    }
283
284
    /**
285
     * @param array|string $query
286
     */
287
    public function setDefaultQuery($query)
288
    {
289
        $this->defaultQuery = $query;
290
    }
291
292
    /**
293
     * @return Collection
294
     */
295
    public function getVersionAliases()
296
    {
297
        return $this->versionAliases;
298
    }
299
300
    /**
301
     * @param Collection $versionAliases
302
     */
303
    public function setVersionAliases(Collection $versionAliases)
304
    {
305
        $this->versionAliases = $versionAliases;
306
    }
307
308
    /**
309
     * @return Collection
310
     */
311
    public function getUsage()
312
    {
313
        return $this->usage;
314
    }
315
316
    /**
317
     * @param Collection $usage
318
     */
319
    public function setUsage(Collection $usage)
320
    {
321
        $this->usage = $usage;
322
    }
323
324
    /**
325
     * @return \Illuminate\Support\Collection
326
     */
327 9
    public function getAliases()
328
    {
329 9
        return $this->aliases;
330
    }
331
332
    /**
333
     * @return \Illuminate\Support\Collection
334
     */
335 4
    public function getBrowsers()
336
    {
337 4
        return $this->browsers;
338
    }
339
340
    /**
341
     * @return \Illuminate\Support\Collection
342
     */
343 9
    public function getData()
344
    {
345 9
        return $this->data;
346
    }
347
348
    /**
349
     * Magic method for running a query by invoking the class
350
     * @param array ...$arguments
351
     * @return \Illuminate\Support\Collection
352
     */
353
    public function __invoke(...$arguments)
354
    {
355
        return $this->query(...$arguments);
356
    }
357
}
358