Passed
Push — master ( ff1fbd...266e25 )
by Jonas
08:06
created

UpdateCommand::legacyVersion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Staudenmeir\DuskUpdater;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Support\Str;
7
use Symfony\Component\Process\Process;
8
use ZipArchive;
9
10
class UpdateCommand extends Command
11
{
12
    use DetectsChromeVersion;
13
14
    /**
15
     * The URL to the latest stable release version.
16
     *
17
     * @var string
18
     */
19
    public static $latestVersionUrl = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE';
20
21
    /**
22
     * The URL to the latest release version for a major Chrome version.
23
     *
24
     * @var string
25
     */
26
    public static $versionUrl = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE_%d';
27
28
    /**
29
     * The URL to the ChromeDriver download.
30
     *
31
     * @var string
32
     */
33
    public static $downloadUrl = 'https://chromedriver.storage.googleapis.com/%s/chromedriver_%s.zip';
34
35
    /**
36
     * The download slugs for the available operating systems.
37
     *
38
     * @var array
39
     */
40
    public static $slugs = [
41
        'linux' => 'linux64',
42
        'mac' => 'mac64',
43
        'win' => 'win32',
44
    ];
45
46
    /**
47
     * The file extensions of the ChromeDriver binaries.
48
     *
49
     * @var array
50
     */
51
    public static $extensions = [
52
        'linux' => '',
53
        'mac' => '',
54
        'win' => '.exe',
55
    ];
56
57
    /**
58
     * The name and signature of the console command.
59
     *
60
     * @var string
61
     */
62
    protected $signature = 'dusk:update {version?}
63
        {--detect= : Detect the installed Chrome/Chromium version, optionally in a custom path}';
64
65
    /**
66
     * The console command description.
67
     *
68
     * @var string
69
     */
70
    protected $description = 'Update the Dusk ChromeDriver binaries';
71
72
    /**
73
     * The path to the binaries directory.
74
     *
75
     * @var string
76
     */
77
    protected $directory = __DIR__.'/../../../laravel/dusk/bin/';
78
79
    /**
80
     * Create a new console command instance.
81
     *
82
     * @return void
83
     */
84 7
    public function __construct()
85
    {
86 7
        if (defined('DUSK_UPDATER_TEST')) {
87 7
            $this->directory = __DIR__.'/../tests/bin/';
88
        }
89
90 7
        parent::__construct();
91 7
    }
92
93
    /**
94
     * Execute the console command.
95
     *
96
     * @return int
97
     */
98 7
    public function handle()
99
    {
100 7
        $detect = $this->input->hasParameterOption('--detect');
101
102 7
        $os = $this->os();
103
104 7
        $version = $this->version($detect, $os);
105
106 7
        if ($version === false) {
0 ignored issues
show
introduced by
The condition $version === false is always false.
Loading history...
107 1
            return 1;
108
        }
109
110 6
        if ($detect && $this->checkVersion($os, $version)) {
111 1
            $this->info(
112 1
                sprintf('No update necessary, your ChromeDriver binary is already on version %s.', $version)
113
            );
114
        } else {
115 6
            $this->update($detect, $os, $version);
116
117 6
            $this->info(
118 6
                sprintf('ChromeDriver %s successfully updated to version %s.', $detect ? 'binary' : 'binaries', $version)
119
            );
120
        }
121
122 6
        return 0;
123
    }
124
125
    /**
126
     * Get the desired ChromeDriver version.
127
     *
128
     * @param bool $detect
129
     * @param string $os
130
     * @return string|bool
131
     */
132 7
    protected function version($detect, $os)
133
    {
134 7
        if ($detect) {
135 3
            $version = $this->chromeVersion($os);
136
137 3
            if ($version === false) {
138 3
                return false;
139
            }
140
        } else {
141 4
            $version = $this->argument('version');
142
143 4
            if (!$version) {
144 1
                return $this->latestVersion();
145
            }
146
147 3
            if (!ctype_digit($version)) {
0 ignored issues
show
Bug introduced by
It seems like $version can also be of type string[]; however, parameter $text of ctype_digit() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

147
            if (!ctype_digit(/** @scrutinizer ignore-type */ $version)) {
Loading history...
148 1
                return $version;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $version also could return the type string[] which is incompatible with the documented return type boolean|string.
Loading history...
149
            }
150
151 2
            $version = (int) $version;
152
        }
153
154 4
        if ($version < 70) {
155 1
            return $this->legacyVersion($version);
156
        }
157
158 3
        $url = sprintf(static::$versionUrl, $version);
159
160 3
        return trim(file_get_contents($url));
161
    }
162
163
    /**
164
     * Get the latest stable ChromeDriver version.
165
     *
166
     * @return string
167
     */
168 1
    protected function latestVersion()
169
    {
170 1
        return trim(file_get_contents(static::$latestVersionUrl));
171
    }
172
173
    /**
174
     * Get the ChromeDriver version for a legacy version of Chrome.
175
     *
176
     * @param int $version
177
     * @return string
178
     */
179 1
    protected function legacyVersion($version)
180
    {
181 1
        $legacy = file_get_contents(__DIR__.'/../resources/legacy.json');
182
183 1
        $legacy = json_decode($legacy, true);
184
185 1
        return $legacy[$version];
186
    }
187
188
    /**
189
     * Check whether the ChromeDriver binary needs to be updated.
190
     *
191
     * @param string $os
192
     * @param string $version
193
     * @return bool
194
     */
195 2
    protected function checkVersion($os, $version)
196
    {
197 2
        $binary = $this->directory.'chromedriver-'.$os.static::$extensions[$os];
198
199 2
        $process = new Process([$binary, '--version']);
200
201 2
        $process->run();
202
203 2
        preg_match('/[\d.]+/', $process->getOutput(), $matches);
204
205 2
        return isset($matches[0]) ? $matches[0] === $version : false;
206
    }
207
208
    /**
209
     * Update the ChromeDriver binaries.
210
     *
211
     * @param bool $detect
212
     * @param string $currentOs
213
     * @param string $version
214
     * @return void
215
     */
216 6
    protected function update($detect, $currentOs, $version)
217
    {
218 6
        foreach (static::$slugs as $os => $slug) {
219 6
            if ($detect && $os !== $currentOs) {
220 2
                continue;
221
            }
222
223 6
            $archive = $this->download($version, $slug);
224
225 6
            $binary = $this->extract($archive);
226
227 6
            $this->rename($binary, $os);
228
        }
229 6
    }
230
231
    /**
232
     * Download the ChromeDriver archive.
233
     *
234
     * @param string $version
235
     * @param string $slug
236
     * @return string
237
     */
238 6
    protected function download($version, $slug)
239
    {
240 6
        $archive = $this->directory.'chromedriver.zip';
241
242 6
        $url = sprintf(static::$downloadUrl, $version, $slug);
243
244 6
        file_put_contents($archive, fopen($url, 'r'));
245
246 6
        return $archive;
247
    }
248
249
    /**
250
     * Extract the ChromeDriver binary from the archive and delete the archive.
251
     *
252
     * @param string $archive
253
     * @return string
254
     */
255 6
    protected function extract($archive)
256
    {
257 6
        $zip = new ZipArchive;
258
259 6
        $zip->open($archive);
260
261 6
        $zip->extractTo($this->directory);
262
263 6
        $binary = $zip->getNameIndex(0);
264
265 6
        $zip->close();
266
267 6
        unlink($archive);
268
269 6
        return $binary;
270
    }
271
272
    /**
273
     * Rename the ChromeDriver binary and make it executable.
274
     *
275
     * @param string $binary
276
     * @param string $os
277
     * @return void
278
     */
279 6
    protected function rename($binary, $os)
280
    {
281 6
        $newName = str_replace('chromedriver', 'chromedriver-'.$os, $binary);
282
283 6
        rename($this->directory.$binary, $this->directory.$newName);
284
285 6
        chmod($this->directory.$newName, 0755);
286 6
    }
287
288
    /**
289
     * Detect the current operating system.
290
     *
291
     * @return string
292
     */
293 7
    protected function os()
294
    {
295 7
        return PHP_OS === 'WINNT' || Str::contains(php_uname(), 'Microsoft')
296
            ? 'win'
297 7
            : (PHP_OS === 'Darwin' ? 'mac' : 'linux');
298
    }
299
}
300