Issues (426)

src/Commands/CapsuleInstall.php (8 issues)

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\Facades\Config;
9
use Illuminate\Support\Str;
10
use Illuminate\Support\Facades\Hash;
11
use A17\Twill\Services\Capsules\Manager;
12
13
class CapsuleInstall extends Command
14
{
15
    /**
16
     * The name and signature of the console command.
17
     *
18
     * @var string
19
     */
20
    protected $signature = 'twill:capsule:install
21
                               {capsule : Capsule name (posts) in plural, Github repository (area17/capsule-posts) or full URL of the Capsule git repository}
22
                               {--require : Require as a Composer package. Can receive maintainer updates.}
23
                               {--copy : Copy Capsule code. Cannot receive updates.}
24
                               {--branch=stable : Repository branch}
25
                               {--prefix=twill-capsule : Capsule repository name prefix}
26
                               {--service=github.com : Service URL (defaults to Github)}';
27
28
    /**
29
     * The console command description.
30
     *
31
     * @var string
32
     */
33
34
    protected $description = 'Install a Twill Capsule';
35
36
    protected $repositoryUri;
37
38
    protected $capsule;
39
40
    protected $capsuleName;
41
42
    protected $repositoryUrl;
43
44
    /**
45
     * Create a new console command instance.
46
     *
47
     * @return void
48
     */
49
    public function __construct()
50
    {
51
        parent::__construct();
52
53
        $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...
54
    }
55
56
    /**
57
     * @return string
58
     */
59
    private function getUnzippedPath(): string
60
    {
61
        return $this->capsule['base_path'] .
62
            '/' .
63
            $this->capsuleName .
64
            '-' .
65
            $this->getBranch();
66
    }
67
68
    /**
69
     * Create super admin account.
70
     *
71
     * @return void
72
     */
73
    public function handle()
74
    {
75
        if (!$this->checkParameters()) {
76
            return 255;
77
        }
78
79
        $this->configureInstaller();
80
81
        $this->displayConfigurationSummary();
82
83
        $this->installCapsule();
84
85
        return 0;
86
    }
87
88
    protected function checkParameters()
89
    {
90
        if (!$this->option('require') && !$this->option('copy')) {
91
            $this->error('Missing mandatory strategy: --require or --copy.');
92
93
            return false;
94
        }
95
96
        if ($this->option('require')) {
97
            $this->error('Require strategy not implemented yet.');
98
99
            return false;
100
        }
101
102
        return true;
103
    }
104
105
    protected function configureInstaller()
106
    {
107
        $capsule = $this->argument('capsule');
108
109
        if ($this->isFullUrl($capsule)) {
110
            $url = $capsule;
111
112
            $capsule = $this->extractRepositoryFromUrl($capsule);
0 ignored issues
show
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

112
            /** @scrutinizer ignore-call */ 
113
            $capsule = $this->extractRepositoryFromUrl($capsule);
Loading history...
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...
113
        } else {
114
            $capsule = Str::snake(Str::kebab($capsule));
0 ignored issues
show
It seems like $capsule can also be of type array; 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

114
            $capsule = Str::snake(Str::kebab(/** @scrutinizer ignore-type */ $capsule));
Loading history...
115
116
            if (!Str::contains($capsule, '/')) {
117
                $capsule = $this->getRepositoryPrefix() . "-$capsule";
118
            }
119
120
            $url = $this->getRepositoryUrlPrefix() . "/$capsule";
121
        }
122
123
        $this->repositoryUri = $capsule;
124
125
        $this->capsuleName = Str::afterLast($capsule, '/');
126
127
        $this->repositoryUrl = $url;
128
129
        $this->name = $this->makeCapsuleName($capsule);
130
131
        $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...
132
133
        $this->capsule = $this->manager->makeCapsule([
134
            'name' => $this->namespace,
135
            'enabled' => true,
136
        ]);
137
    }
138
139
    protected function isFullUrl($capsule)
0 ignored issues
show
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

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

390
    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...
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

390
    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...
391
    {
392
        $this->info('Unzipping with unzip shell command...');
393
394
        chdir($this->capsule['base_path']);
395
396
        shell_exec('unzip install.zip');
397
398
        return file_exists($this->getUnzippedPath());
399
    }
400
401
    public function renameToCapsule()
402
    {
403
        $destination = $this->capsule['psr4_path'];
404
405
        rename($this->getUnzippedPath(), $destination);
406
407
        return file_exists($destination);
408
    }
409
410
    public function makeDir($path)
411
    {
412
        if (!file_exists($path)) {
413
            mkdir($path, 0775, true);
414
        }
415
    }
416
}
417