Completed
Pull Request — master (#155)
by Thomas
12:04
created

BrowscapUpdater::getLoader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * Copyright (c) 1998-2015 Browser Capabilities Project
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a
6
 * copy of this software and associated documentation files (the "Software"),
7
 * to deal in the Software without restriction, including without limitation
8
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
 * and/or sell copies of the Software, and to permit persons to whom the
10
 * Software is furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included
13
 * in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
 * THE SOFTWARE.
22
 *
23
 * @category   Browscap-PHP
24
 * @package    Browscap
25
 * @copyright  1998-2015 Browser Capabilities Project
26
 * @license    http://www.opensource.org/licenses/MIT MIT License
27
 * @link       https://github.com/browscap/browscap-php/
28
 */
29
30
namespace BrowscapPHP;
31
32
use Browscap\Generator\BuildGenerator;
33
use Browscap\Helper\CollectionCreator;
34
use Browscap\Writer\Factory\PhpWriterFactory;
35
use BrowscapPHP\Cache\BrowscapCache;
36
use BrowscapPHP\Cache\BrowscapCacheInterface;
37
use BrowscapPHP\Exception\FetcherException;
38
use BrowscapPHP\Helper\Converter;
39
use BrowscapPHP\Helper\Filesystem;
40
use BrowscapPHP\Helper\IniLoader;
41
use BrowscapPHP\Helper\Quoter;
42
use BrowscapPHP\Parser\ParserInterface;
43
use Psr\Log\LoggerInterface;
44
use Psr\Log\NullLogger;
45
use WurflCache\Adapter\AdapterInterface;
46
use WurflCache\Adapter\File;
47
48
/**
49
 * Browscap.ini parsing class with caching and update capabilities
50
 *
51
 * @category   Browscap-PHP
52
 * @package    Browscap
53
 * @author     Jonathan Stoppani <[email protected]>
54
 * @author     Vítor Brandão <[email protected]>
55
 * @author     Mikołaj Misiurewicz <[email protected]>
56
 * @author     Christoph Ziegenberg <[email protected]>
57
 * @author     Thomas Müller <[email protected]>
58
 * @copyright  Copyright (c) 1998-2015 Browser Capabilities Project
59
 * @version    3.0
60
 * @license    http://www.opensource.org/licenses/MIT MIT License
61
 * @link       https://github.com/browscap/browscap-php/
62
 */
63
class BrowscapUpdater
64
{
65
    /**
66
     * The cache instance
67
     *
68
     * @var \BrowscapPHP\Cache\BrowscapCacheInterface
69
     */
70
    private $cache = null;
71
72
    /**
73
     * @var @var \Psr\Log\LoggerInterface
74
     */
75
    private $logger = null;
76
77
    /**
78
     * Options for the updater. The array should be overwritten,
79
     * containing all options as keys, set to the default value.
80
     *
81
     * @var array
82
     */
83
    private $options = array();
84
85
    /**
86
     * @var \BrowscapPHP\Helper\IniLoader
87
     */
88
    private $loader = null;
89
90
    /**
91
     * Gets a cache instance
92
     *
93
     * @return \BrowscapPHP\Cache\BrowscapCacheInterface
94
     */
95 14 View Code Duplication
    public function getCache()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
96
    {
97 14
        if (null === $this->cache) {
98 1
            $cacheDirectory = __DIR__.'/../resources/';
99
100 1
            $cacheAdapter = new File(
101 1
                array(File::DIR => $cacheDirectory)
102 1
            );
103
104 1
            $this->cache = new BrowscapCache($cacheAdapter);
105
        }
106
107 14
        return $this->cache;
108
    }
109
110
    /**
111
     * Sets a cache instance
112
     *
113
     * @param \BrowscapPHP\Cache\BrowscapCacheInterface|\WurflCache\Adapter\AdapterInterface $cache
114
     *
115
     * @throws \BrowscapPHP\Exception
116
     * @return \BrowscapPHP\BrowscapUpdater
117
     */
118 14 View Code Duplication
    public function setCache($cache)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
119
    {
120 14
        if ($cache instanceof BrowscapCacheInterface) {
121 10
            $this->cache = $cache;
122 14
        } elseif ($cache instanceof AdapterInterface) {
123 3
            $this->cache = new BrowscapCache($cache);
124 3
        } else {
125 1
            throw new Exception(
126
                'the cache has to be an instance of \BrowscapPHP\Cache\BrowscapCacheInterface or '
127 1
                .'an instanceof of \WurflCache\Adapter\AdapterInterface',
128
                Exception::CACHE_INCOMPATIBLE
129 1
            );
130
        }
131
132 13
        return $this;
133
    }
134
135
    /**
136
     * Sets a logger instance
137
     *
138
     * @param \Psr\Log\LoggerInterface $logger
139
     *
140
     * @return \BrowscapPHP\BrowscapUpdater
141
     */
142 10
    public function setLogger(LoggerInterface $logger)
143
    {
144 10
        $this->logger = $logger;
145
146 10
        return $this;
147
    }
148
149
    /**
150
     * returns a logger instance
151
     *
152
     * @return \Psr\Log\LoggerInterface
153
     */
154 13
    public function getLogger()
155
    {
156 13
        if (null === $this->logger) {
157 3
            $this->logger = new NullLogger();
158
        }
159
160 13
        return $this->logger;
161
    }
162
163
    /**
164
     * Sets multiple loader options at once
165
     *
166
     * @param array $options
167
     *
168
     * @return \BrowscapPHP\BrowscapUpdater
169
     */
170 6
    public function setOptions(array $options)
171
    {
172 6
        $this->options = $options;
173
174 1
        return $this;
175
    }
176
177
    /**
178
     * @return \BrowscapPHP\Helper\IniLoader
179
     */
180 9
    public function getLoader()
181
    {
182 9
        if (null === $this->loader) {
183 1
            $this->loader = new IniLoader();
184
        }
185
186 9
        return $this->loader;
187
    }
188
189
    /**
190
     * @param \BrowscapPHP\Helper\IniLoader $loader
191
     */
192 9
    public function setLoader(IniLoader $loader)
193
    {
194 9
        $this->loader = $loader;
195 9
    }
196
197
    /**
198
     * reads and parses an ini file and writes the results into the cache
199
     *
200
     * @param  string $iniFile
201
     *
202
     * @throws \BrowscapPHP\Exception
203
     */
204 4
    public function convertFile($iniFile)
205
    {
206 4
        $loader = new IniLoader();
207
208
        try {
209 4
            $loader->setLocalFile($iniFile);
210 4
        } catch (Helper\Exception $e) {
211 1
            throw new Exception('an error occured while setting the local file', 0, $e);
212
        }
213
214
        try {
215 3
            $iniString = $loader->load();
216 3
        } catch (Helper\Exception $e) {
217 1
            throw new Exception('an error occured while converting the local file into the cache', 0, $e);
218
        }
219
220 2
        $this->convertString($iniString);
221 2
    }
222
223
    /**
224
     * reads and parses an ini string and writes the results into the cache
225
     *
226
     * @param string $iniString
227
     */
228 3
    public function convertString($iniString)
229
    {
230 3
        $cachedVersion = $this->getCache()->getItem('browscap.version', false, $success);
231 3
        $converter     = new Converter($this->getLogger(), $this->getCache());
232
233 3
        $this->storeContent($converter, $iniString, $cachedVersion);
234 3
    }
235
236
    /**
237
     * fetches a remote file and stores it into a local folder
238
     *
239
     * @param string $file       The name of the file where to store the remote content
240
     * @param string $remoteFile The code for the remote file to load
241
     *
242
     * @throws \BrowscapPHP\Exception\FetcherException
243
     * @throws \BrowscapPHP\Helper\Exception
244
     */
245 3
    public function fetch($file, $remoteFile = IniLoader::PHP_INI)
246
    {
247 3
        if (null === ($cachedVersion = $this->checkUpdate($remoteFile))) {
248
            // no newer version available
249
            return;
250
        }
251
252 3
        $this->getLoader()
253 3
            ->setRemoteFilename($remoteFile)
254 3
            ->setOptions($this->options)
255 3
            ->setLogger($this->getLogger())
256
        ;
257
258 3
        $this->getLogger()->debug('started fetching remote file');
259
260
        try {
261 3
            $content = $this->getLoader()->load();
262 3
        } catch (Helper\Exception $e) {
263
            throw new FetcherException('an error occured while fetching remote data', 0, $e);
264
        }
265
266 3 View Code Duplication
        if (false === $content) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
267 1
            $error = error_get_last();
268 1
            throw FetcherException::httpError($this->getLoader()->getRemoteIniUrl(), $error['message']);
269
        }
270
271 2
        $this->getLogger()->debug('finished fetching remote file');
272 2
        $this->getLogger()->debug('started storing remote file into local file');
273
274 2
        $content = $this->sanitizeContent($content);
275
276 2
        $converter  = new Converter($this->getLogger(), $this->getCache());
277 2
        $iniVersion = $converter->getIniVersion($content);
278
279 2
        if ($iniVersion > $cachedVersion) {
280 2
            $fs = new Filesystem();
281 2
            $fs->dumpFile($file, $content);
282
        }
283
284 2
        $this->getLogger()->debug('finished storing remote file into local file');
285 2
    }
286
287
    /**
288
     * fetches a remote file, parses it and writes the result into the cache
289
     *
290
     * if the local stored information are in the same version as the remote data no actions are
291
     * taken
292
     *
293
     * @param string      $remoteFile The code for the remote file to load
294
     * @param string|null $buildFolder
295
     * @param int|null    $buildNumber
296
     *
297
     * @throws \BrowscapPHP\Exception\FileNotFoundException
298
     * @throws \BrowscapPHP\Helper\Exception
299
     * @throws \BrowscapPHP\Exception\FetcherException
300
     */
301 2
    public function update($remoteFile = IniLoader::PHP_INI, $buildFolder = null, $buildNumber = null)
302
    {
303 2
        $this->getLogger()->debug('started fetching remote file');
304
305 2
        $converter = new Converter($this->getLogger(), $this->getCache());
306
307 2
        if (class_exists('\Browscap\Browscap')) {
308
            $resourceFolder = 'vendor/browscap/browscap/resources/';
309
310
            if (null === $buildNumber) {
311
                $buildNumber = (int)file_get_contents('vendor/browscap/browscap/BUILD_NUMBER');
312
            }
313
314
            if (null === $buildFolder) {
315
                $buildFolder = 'resources';
316
            }
317
318
            $buildFolder .= '/browscap-ua-test-'.$buildNumber;
319
            $iniFile     = $buildFolder.'/full_php_browscap.ini';
320
321
            mkdir($buildFolder, 0777, true);
322
323
            $writerCollectionFactory = new PhpWriterFactory();
324
            $writerCollection        = $writerCollectionFactory->createCollection($this->getLogger(), $buildFolder);
325
326
            $buildGenerator = new BuildGenerator($resourceFolder, $buildFolder);
327
            $buildGenerator
328
                ->setLogger($this->getLogger())
329
                ->setCollectionCreator(new CollectionCreator())
330
                ->setWriterCollection($writerCollection)
331
                ->run($buildNumber, false)
332
            ;
333
334
            $converter
335
                ->setVersion($buildNumber)
336
                ->storeVersion()
337
                ->convertFile($iniFile)
338
            ;
339
340
            $filesystem = new Filesystem();
341
            $filesystem->remove($buildFolder);
342
        } else {
343 2
            if (null === ($cachedVersion = $this->checkUpdate($remoteFile))) {
344
                // no newer version available
345
                return;
346
            }
347
348 2
            $this->getLoader()
349 2
                ->setRemoteFilename($remoteFile)
350 2
                ->setOptions($this->options)
351 2
                ->setLogger($this->getLogger())
352
            ;
353
354
            try {
355 2
                $content = $this->getLoader()->load();
356 2
            } catch (Helper\Exception $e) {
357
                throw new FetcherException('an error occured while loading remote data', 0, $e);
358
            }
359
360 2 View Code Duplication
            if (false === $content) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
361 1
                $internalLoader = $this->getLoader()->getLoader();
362 1
                $error          = error_get_last();
363
364 1
                throw FetcherException::httpError($internalLoader->getUri(), $error['message']);
365
            }
366
367 1
            $this->getLogger()->debug('finished fetching remote file');
368
369 1
            $this->storeContent($converter, $content, $cachedVersion);
370
        }
371 1
    }
372
373
    /**
374
     * checks if an update on a remote location for the local file or the cache
375
     *
376
     * @param string $remoteFile
377
     *
378
     * @return int|null The actual cached version if a newer version is available, null otherwise
379
     * @throws \BrowscapPHP\Helper\Exception
380
     * @throws \BrowscapPHP\Exception\FetcherException
381
     */
382 9
    public function checkUpdate($remoteFile = IniLoader::PHP_INI)
383
    {
384 9
        $success       = null;
385 9
        $cachedVersion = $this->getCache()->getItem('browscap.version', false, $success);
386
387 9
        if (!$cachedVersion) {
388
            // could not load version from cache
389 5
            $this->getLogger()->info('there is no cached version available, please update from remote');
390
391 5
            return 0;
392
        }
393
394 4
        $this->getLoader()
395 4
            ->setRemoteFilename($remoteFile)
396 4
            ->setOptions($this->options)
397 4
            ->setLogger($this->getLogger())
398
        ;
399
400
        try {
401 4
            $remoteVersion = $this->getLoader()->getRemoteVersion();
402 4
        } catch (Helper\Exception $e) {
403 1
            throw new FetcherException('an error occured while checking remote version', 0, $e);
404
        }
405
406 3
        if (!$remoteVersion) {
407
            // could not load remote version
408
            $this->getLogger()->info('could not load version from remote location');
409
410
            return 0;
411
        }
412
413 3
        if ($cachedVersion && $remoteVersion && $remoteVersion <= $cachedVersion) {
414
            // no newer version available
415 1
            $this->getLogger()->info('there is no newer version available');
416
417 1
            return null;
418
        }
419
420 2
        $this->getLogger()->info(
421 2
            'a newer version is available, local version: ' . $cachedVersion . ', remote version: ' . $remoteVersion
422 2
        );
423
424 2
        return (int) $cachedVersion;
425
    }
426
427
    /**
428
     * @param string $content
429
     *
430
     * @return mixed
431
     */
432 6
    private function sanitizeContent($content)
433
    {
434
        // replace everything between opening and closing php and asp tags
435 6
        $content = preg_replace('/<[?%].*[?%]>/', '', $content);
436
437
        // replace opening and closing php and asp tags
438 6
        return str_replace(array('<?', '<%', '?>', '%>'), '', $content);
439
    }
440
441
    /**
442
     * reads and parses an ini string and writes the results into the cache
443
     *
444
     * @param \BrowscapPHP\Helper\Converter $converter
445
     * @param string                        $content
446
     * @param int|null                      $cachedVersion
447
     */
448 4
    private function storeContent(Converter $converter, $content, $cachedVersion)
449
    {
450 4
        $iniString  = $this->sanitizeContent($content);
451 4
        $iniVersion = $converter->getIniVersion($iniString);
452
453 4
        if (!$cachedVersion || $iniVersion > $cachedVersion) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cachedVersion of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
454
            $converter
455 4
                ->storeVersion()
456 4
                ->convertString($iniString)
457
            ;
458
        }
459 4
    }
460
}
461