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