Completed
Push — 5.6 ( 679697...f4d50c )
by Jeroen
16:35 queued 10:49
created

VersionChecker::check()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 4.0105

Importance

Changes 0
Metric Value
dl 0
loc 38
ccs 21
cts 23
cp 0.913
rs 9.312
c 0
b 0
f 0
cc 4
nc 8
nop 0
crap 4.0105
1
<?php
2
3
namespace Kunstmaan\AdminBundle\Helper\VersionCheck;
4
5
use Doctrine\Common\Cache\Cache;
6
use Exception;
7
use GuzzleHttp\Client;
8
use Kunstmaan\AdminBundle\Helper\VersionCheck\Exception\ParseException;
9
use Symfony\Component\DependencyInjection\ContainerInterface;
10
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslatorInterface;
11
use Symfony\Contracts\Translation\TranslatorInterface;
12
13
/**
14
 * Version checker
15
 */
16
class VersionChecker
17
{
18
    /**
19
     * @var ContainerInterface
20
     */
21
    private $container;
22
23
    /**
24
     * @var Cache
25
     */
26
    private $cache;
27
28
    /**
29
     * @var string
30
     */
31
    private $webserviceUrl;
32
33
    /**
34
     * @var int
35
     */
36
    private $cacheTimeframe;
37
38
    /**
39
     * @var bool
40
     */
41
    private $enabled;
42
43
    /**
44
     * @var Client
45
     */
46
    private $client;
47
48
    /**
49
     * @var TranslatorInterface|LegacyTranslatorInterface
50
     */
51
    private $translator;
52
53
    /**
54
     * Constructor
55
     */
56 7
    public function __construct(ContainerInterface $container, Cache $cache, $translator)
57
    {
58 7
        $this->container = $container;
59 7
        $this->cache = $cache;
60
61
        // NEXT_MAJOR Add "Symfony\Contracts\Translation\TranslatorInterface" typehint when sf <4.4 support is removed.
62 7
        if (!$translator instanceof TranslatorInterface && !$translator instanceof LegacyTranslatorInterface) {
63
            throw new \InvalidArgumentException(sprintf('The "$translator" parameter should be instance of "%s" or "%s"', TranslatorInterface::class, LegacyTranslatorInterface::class));
64
        }
65
66 7
        $this->translator = $translator;
67
68 7
        $this->webserviceUrl = $this->container->getParameter('version_checker.url');
69 7
        $this->cacheTimeframe = $this->container->getParameter('version_checker.timeframe');
70 7
        $this->enabled = $this->container->getParameter('version_checker.enabled');
71 7
    }
72
73
    /**
74
     * Check that the version check is enabled.
75
     *
76
     * @return bool
77
     */
78 7
    public function isEnabled()
79
    {
80 7
        return $this->enabled;
81
    }
82
83
    /**
84
     * Check if we recently did a version check, if not do one now.
85
     *
86
     * @throws ParseException
87
     */
88 1
    public function periodicallyCheck()
89
    {
90 1
        if (!$this->isEnabled()) {
91
            return;
92
        }
93
94 1
        $data = $this->cache->fetch('version_check');
95 1
        if (!\is_array($data)) {
96
            $this->check();
97
        }
98 1
    }
99
100
    /**
101
     * Get the version details via webservice.
102
     *
103
     * @return mixed a list of bundles if available
104
     *
105
     * @throws ParseException
106
     */
107 5
    public function check()
108
    {
109 5
        if (!$this->isEnabled()) {
110
            return;
111
        }
112
113 5
        $host = $this->container->get('request_stack')->getCurrentRequest()->getHttpHost();
114 5
        $console = realpath($this->container->get('kernel')->getProjectDir() . '/bin/console');
115 5
        $installed = filectime($console);
116 5
        $bundles = $this->parseComposer();
117 2
        $title = $this->container->getParameter('kunstmaan_admin.website_title');
118
119 2
        $jsonData = json_encode([
120 2
            'host' => $host,
121 2
            'installed' => $installed,
122 2
            'bundles' => $bundles,
123 2
            'project' => $this->translator->trans($title),
124
        ]);
125
126
        try {
127 2
            $client = $this->getClient();
128 2
            $response = $client->post($this->webserviceUrl, ['body' => $jsonData]);
129 1
            $contents = $response->getBody()->getContents();
130 1
            $data = json_decode($contents);
131
132 1
            if (null === $data) {
133
                return false;
134
            }
135
136
            // Save the result in the cache to make sure we don't do the check too often
137 1
            $this->cache->save('version_check', $data, $this->cacheTimeframe);
138
139 1
            return $data;
140 1
        } catch (Exception $e) {
141
            // We did not receive valid json
142 1
            return false;
143
        }
144
    }
145
146
    /**
147
     * @return Client
148
     */
149 1
    public function getClient()
150
    {
151 1
        if (!$this->client) {
152 1
            $this->client = new Client(['connect_timeout' => 3, 'timeout' => 1]);
153
        }
154
155 1
        return $this->client;
156
    }
157
158
    /**
159
     * @param Client $client
160
     */
161
    public function setClient($client)
162
    {
163
        $this->client = $client;
164
    }
165
166
    /**
167
     * Returns the absolute path to the composer.lock file.
168
     *
169
     * @return string
170
     */
171
    protected function getLockPath()
172
    {
173
        $kernel = $this->container->get('kernel');
174
        $rootPath = $kernel->getProjectDir();
175
176
        return $rootPath . '/composer.lock';
177
    }
178
179
    /**
180
     * Returns a list of composer packages.
181
     *
182
     * @return array
183
     *
184
     * @throws ParseException
185
     */
186 4
    protected function getPackages()
187
    {
188 4
        $translator = $this->container->get('translator');
189 4
        $errorMessage = $translator->trans('settings.version.error_parsing_composer');
190
191 4
        $composerPath = $this->getLockPath();
192 4
        if (!file_exists($composerPath)) {
193 1
            throw new ParseException($translator->trans('settings.version.composer_lock_not_found'));
194
        }
195
196 3
        $json = file_get_contents($composerPath);
197 3
        $result = json_decode($json, true);
198
199 3
        if (json_last_error() !== JSON_ERROR_NONE) {
200 1
            throw new ParseException($errorMessage . ' (#' . json_last_error() . ')');
201
        }
202
203 2
        if (\array_key_exists('packages', $result) && \is_array($result['packages'])) {
204 1
            return $result['packages'];
205
        }
206
207
        // No package list in JSON structure
208 1
        throw new ParseException($errorMessage);
209
    }
210
211
    /**
212
     * Parse the composer.lock file to get the currently used versions of the kunstmaan bundles.
213
     *
214
     * @return array
215
     *
216
     * @throws ParseException
217
     */
218 4
    protected function parseComposer()
219
    {
220 4
        $bundles = [];
221 4
        foreach ($this->getPackages() as $package) {
222 1
            if (!strncmp($package['name'], 'kunstmaan/', \strlen('kunstmaan/'))) {
223 1
                $bundles[] = [
224 1
                    'name' => $package['name'],
225 1
                    'version' => $package['version'],
226 1
                    'reference' => $package['source']['reference'],
227
                ];
228
            }
229
        }
230
231 1
        return $bundles;
232
    }
233
}
234