Passed
Push — master ( 1438ee...6641ad )
by Quentin
06:42 queued 12s
created

CapsuleInstall::uncompress()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
nc 3
nop 2
dl 0
loc 17
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace A17\Twill\Commands;
4
5
use Exception;
6
use GuzzleHttp\Client;
7
use A17\Twill\Models\User;
8
use Illuminate\Support\Str;
9
use Illuminate\Support\Facades\Hash;
10
use A17\Twill\Services\Capsules\Manager;
11
12
class CapsuleInstall extends Command
13
{
14
    /**
15
     * The name and signature of the console command.
16
     *
17
     * @var string
18
     */
19
    protected $signature = 'twill:capsule:install 
20
                               {capsule : Capsule name (posts) in plural, Github repository (area17/capsule-posts) or full URL of the Capsule git repository}
21
                               {--require : Require as a Composer package. Can receive maitainer updates.} 
22
                               {--copy : Copy Capsule code. Cannot receive updates.}
23
                               {--branch=stable : Repository branch}
24
                               {--prefix=twill-capsule : Capsule repository name prefix}
25
                               {--service=github.com : Service URL (defaults to Github)}';
26
27
    /**
28
     * The console command description.
29
     *
30
     * @var string
31
     */
32
33
    protected $description = 'Install a Twill Capsule';
34
35
    protected $repositoryUri;
36
37
    protected $capsule;
38
39
    protected $capsuleName;
40
41
    protected $repositoryUrl;
42
43
    /**
44
     * Create a new console command instance.
45
     *
46
     * @return void
47
     */
48
    public function __construct()
49
    {
50
        parent::__construct();
51
52
        $this->manager = new Manager();
0 ignored issues
show
Bug Best Practice introduced by
The property manager does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
53
    }
54
55
    /**
56
     * @return string
57
     */
58
    private function getUnzippedPath(): string
59
    {
60
        return $this->capsule['base_path'] .
61
            '/' .
62
            $this->capsuleName .
63
            '-' .
64
            $this->getBranch();
65
    }
66
67
    /**
68
     * Create super admin account.
69
     *
70
     * @return void
71
     */
72
    public function handle()
73
    {
74
        if (!$this->checkParameters()) {
75
            return 255;
76
        }
77
78
        $this->configureInstaller();
79
80
        $this->displayConfigurationSummary();
81
82
        $this->installCapsule();
83
84
        return 0;
85
    }
86
87
    protected function checkParameters()
88
    {
89
        if (!$this->option('require') && !$this->option('copy')) {
90
            $this->error('Missing mandatory strategy: --require or --copy.');
91
92
            return false;
93
        }
94
95
        if ($this->option('require')) {
96
            $this->error('Require strategy not implemented yet.');
97
98
            return false;
99
        }
100
101
        return true;
102
    }
103
104
    protected function configureInstaller()
105
    {
106
        $capsule = $this->argument('capsule');
107
108
        if ($this->isFullUrl($capsule)) {
109
            $url = $capsule;
110
111
            $capsule = $this->extractRepositoryFromUrl($capsule);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $capsule is correct as $this->extractRepositoryFromUrl($capsule) targeting A17\Twill\Commands\CapsuleInstall::__call() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
The method extractRepositoryFromUrl() does not exist on A17\Twill\Commands\CapsuleInstall. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

111
            /** @scrutinizer ignore-call */ 
112
            $capsule = $this->extractRepositoryFromUrl($capsule);
Loading history...
112
        } else {
113
            $capsule = Str::snake(Str::kebab($capsule));
0 ignored issues
show
Bug introduced by
It seems like $capsule can also be of type string[]; however, parameter $value of Illuminate\Support\Str::kebab() 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

113
            $capsule = Str::snake(Str::kebab(/** @scrutinizer ignore-type */ $capsule));
Loading history...
114
115
            if (!Str::contains($capsule, '/')) {
116
                $capsule = $this->getAREA17RepositoryPrefix() . "-$capsule";
117
            }
118
119
            $url = $this->getRepositoryUrlPrefix() . "/$capsule";
120
        }
121
122
        $this->repositoryUri = $capsule;
123
124
        $this->capsuleName = Str::afterLast($capsule, '/');
125
126
        $this->repositoryUrl = $url;
127
128
        $this->name = $this->makeCapsuleName($capsule);
129
130
        $this->namespace = Str::studly($this->name);
0 ignored issues
show
Bug Best Practice introduced by
The property namespace does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
131
132
        $this->capsule = $this->manager->makeCapsule([
133
            'name' => $this->namespace,
134
            'enabled' => true,
135
        ]);
136
    }
137
138
    protected function isFullUrl($capsule)
0 ignored issues
show
Unused Code introduced by
The parameter $capsule is not used and could be removed. ( Ignorable by Annotation )

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

138
    protected function isFullUrl(/** @scrutinizer ignore-unused */ $capsule)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
139
    {
140
        return false;
141
    }
142
143
    protected function getRepositoryUrlPrefix()
144
    {
145
        return 'https://' . $this->getService();
146
    }
147
148
    protected function getAREA17RepositoryPrefix()
149
    {
150
        $prefix = 'area17';
151
152
        if (filled($capsule = $this->getCapsulePrefix())) {
153
            $prefix .= '/' . $this->getCapsulePrefix();
154
        }
155
156
        return $prefix;
157
    }
158
159
    protected function getBranch()
160
    {
161
        return $this->option('branch');
162
    }
163
164
    protected function getZipAddress()
165
    {
166
        return sprintf(
167
            '%s/archive/refs/heads/%s.zip',
168
            $this->repositoryUrl,
169
            $this->getBranch()
170
        );
171
    }
172
173
    protected function makeCapsuleName($capsule)
174
    {
175
        $capsule = Str::afterLast($capsule, '/');
176
177
        return Str::after($capsule, $this->getCapsulePrefix() . '-');
178
    }
179
180
    protected function getCapsulePrefix()
181
    {
182
        return $this->option('prefix');
183
    }
184
185
    public function getService()
186
    {
187
        return $this->option('service');
188
    }
189
190
    protected function displayConfigurationSummary()
191
    {
192
        $this->info('Configuration summary');
193
194
        $this->info('---------------------');
195
196
        $this->info('Name prefix: ' . $this->getCapsulePrefix());
197
198
        $this->info("Capsule repository URI: {$this->repositoryUri}");
199
200
        $this->info("Capsule name: {$this->capsuleName}");
201
202
        $this->info("Name: {$this->name}");
203
204
        $this->info('Module: ' . $this->getModule());
205
206
        $this->info("Namespace: {$this->namespace}");
207
208
        $this->info('Service: ' . $this->getService());
209
210
        $this->info('Branch: ' . $this->getBranch());
211
212
        $this->info("Repository URL: {$this->repositoryUrl}");
213
214
        $this->info('Zip URL: ' . $this->getZipAddress());
215
216
        $this->info('Temporary file: ' . $this->getTempFileName());
217
    }
218
219
    protected function getModule()
220
    {
221
        return Str::camel($this->name);
222
    }
223
224
    protected function canInstallCapsule()
225
    {
226
        if ($this->manager->capsuleExists($this->getModule())) {
227
            $this->error('A capsule with this name already exists!');
228
229
            return false;
230
        }
231
232
        if ($this->directoryExists()) {
233
            $this->error(
234
                'Capsule directory already exists: ' .
235
                    $this->getCapsuleDirectory()
236
            );
237
238
            return false;
239
        }
240
241
        return true;
242
    }
243
244
    protected function installCapsule()
245
    {
246
        $installed =
247
            $this->canInstallCapsule() &&
248
            $this->download() &&
249
            $this->uncompress(
250
                $this->getTempFileName(),
251
                $this->capsule['base_path']
252
            ) &&
253
            $this->renameToCapsule();
254
255
        $this->comment('');
256
257
        if (!$installed) {
258
            $this->error('Your capsule was not installed.');
259
        } else {
260
            $this->comment('Your capsule was installed successfully!');
261
        }
262
263
        return $installed ? 0 : 255;
264
    }
265
266
    protected function getCapsuleDirectory()
267
    {
268
        return $this->capsule['root_path'];
269
    }
270
271
    protected function directoryExists()
272
    {
273
        return file_exists($this->getCapsuleDirectory());
274
    }
275
276
    protected function download()
277
    {
278
        if (!$this->cleanTempFile() || !$this->repositoryExists()) {
279
            return false;
280
        }
281
282
        $this->info('Downloading zip file...');
283
284
        file_put_contents(
285
            $this->getTempFileName(),
286
            fopen($this->getZipAddress(), 'r')
287
        );
288
289
        return true;
290
    }
291
292
    protected function cleanTempFile()
293
    {
294
        if (file_exists($this->getTempFileName())) {
295
            unlink($this->getTempFileName());
296
297
            if (file_exists($this->getTempFileName())) {
298
                $this->error(
299
                    'Unable to remove temporary file: ' .
300
                        $this->getTempFileName()
301
                );
302
303
                return false;
304
            }
305
        }
306
307
        return true;
308
    }
309
310
    protected function getTempFileName()
311
    {
312
        return $this->capsule['base_path'] . '/install.zip';
313
    }
314
315
    protected function repositoryExists()
316
    {
317
        $guzzle = new Client();
318
319
        try {
320
            $statusCode = $guzzle
321
                ->request('GET', $this->repositoryUrl)
322
                ->getStatusCode();
323
        } catch (Exception $exception) {
324
            $statusCode = $exception->getCode();
325
        }
326
327
        if ($statusCode !== 200) {
328
            $this->error('Repository not found: ' . $this->repositoryUrl);
329
330
            return false;
331
        }
332
333
        return true;
334
    }
335
336
    protected function uncompress($zip, $directory)
337
    {
338
        $this->info('Unzipping file...');
339
340
        if (extension_loaded('zip')) {
341
            return $this->unzipWithExtension($zip, $directory);
342
        }
343
344
        if ($this->unzipShellCommandExists()) {
345
            return $this->unzipWithShell($zip, $directory);
346
        }
347
348
        $this->error(
349
            'Zip extension not installed and unzip command not found.'
350
        );
351
352
        return false;
353
    }
354
355
    protected function unzipShellCommandExists()
356
    {
357
        $return = shell_exec('which unzip');
358
359
        return !empty($return);
360
    }
361
362
    protected function unzipWithExtension($zip, $directory)
363
    {
364
        $this->info('Unzipping with PHP zip extension...');
365
366
        $unzip = new \ZipArchive();
367
368
        $success = $unzip->open($zip) && $unzip->extractTo("$directory/");
369
370
        try {
371
            $unzip->close();
372
        } catch (Exception $exception) {
373
            //
374
        }
375
376
        unlink($zip);
377
378
        if (!$success) {
379
            $this->error("Cound not read zip file: $zip");
380
381
            return false;
382
        }
383
384
        return true;
385
    }
386
387
    protected function unzipWithShell($zip, $directory)
0 ignored issues
show
Unused Code introduced by
The parameter $directory is not used and could be removed. ( Ignorable by Annotation )

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

387
    protected function unzipWithShell($zip, /** @scrutinizer ignore-unused */ $directory)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $zip is not used and could be removed. ( Ignorable by Annotation )

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

387
    protected function unzipWithShell(/** @scrutinizer ignore-unused */ $zip, $directory)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
388
    {
389
        $this->info('Unzipping with unzip shell command...');
390
391
        chdir($this->capsule['base_path']);
392
393
        shell_exec('unzip install.zip');
394
395
        return file_exists($this->getUnzippedPath());
396
    }
397
398
    public function renameToCapsule()
399
    {
400
        $destination = $this->capsule['psr4_path'];
401
402
        rename($this->getUnzippedPath(), $destination);
403
404
        return file_exists($destination);
405
    }
406
}
407