Completed
Push — composer-script ( 432be6...9c5893 )
by Eric
64:29 queued 11s
created

CKEditorInstallerCommand::info()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Ivory CKEditor package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\CKEditorBundle\Command;
13
14
use Ivory\CKEditorBundle\Installer\CKEditorInstaller;
15
use Symfony\Component\Console\Command\Command;
16
use Symfony\Component\Console\Helper\ProgressBar;
17
use Symfony\Component\Console\Helper\ProgressHelper;
18
use Symfony\Component\Console\Helper\QuestionHelper;
19
use Symfony\Component\Console\Input\InputArgument;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\Console\Question\ChoiceQuestion;
24
25
/**
26
 * @author GeLo <[email protected]>
27
 */
28
class CKEditorInstallerCommand extends Command
29
{
30
    /**
31
     * @var CKEditorInstaller
32
     */
33
    private $installer;
34
35
    /**
36
     * @param CKEditorInstaller|null $installer
37
     */
38
    public function __construct(CKEditorInstaller $installer = null)
39
    {
40
        parent::__construct();
41
42
        $this->installer = $installer ?: new CKEditorInstaller();
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    protected function configure()
49
    {
50
        $this
51
            ->setName('ckeditor:install')
52
            ->setDescription('Install CKEditor')
53
            ->addArgument('path', InputArgument::OPTIONAL, 'Where to install CKEditor')
54
            ->addOption('release', null, InputOption::VALUE_OPTIONAL, 'CKEditor release (basic, standard or full)')
55
            ->addOption('tag', null, InputOption::VALUE_OPTIONAL, 'CKEditor tag (x.y.z or latest)')
56
            ->addOption(
57
                'clear',
58
                null,
59
                InputOption::VALUE_OPTIONAL,
60
                'How to clear previous CKEditor installation (drop, keep or skip)'
61
            )
62
            ->addOption(
63
                'exclude',
64
                null,
65
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
66
                'Path to exclude when extracting CKEditor'
67
            )
68
            ->setHelp(<<<'EOF'
69
The <info>%command.name%</info> command install CKEditor in your application:
70
71
  <info>php %command.full_name%</info>
72
  
73
You can install it at a specific path (absolute):
74
75
  <info>php %command.full_name% path</info>
76
  
77
You can install a specific release (basic, standard or full):
78
79
  <info>php %command.full_name% --release=full</info>
80
  
81
You can install a specific version:
82
83
  <info>php %command.full_name% --tag=4.7.0</info>
84
85
If there is a previous CKEditor installation detected, 
86
you can control how it should be handled in non-interactive mode:
87
88
  <info>php %command.full_name% --clear=drop</info>
89
  <info>php %command.full_name% --clear=keep</info>
90
  <info>php %command.full_name% --clear=skip</info>
91
  
92
You can exclude path(s) when extracting CKEditor:
93
94
  <info>php %command.full_name% --exclude=samples --exclude=adapters</info>
95
EOF
96
            );
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    protected function execute(InputInterface $input, OutputInterface $output)
103
    {
104
        $this->title($output);
105
106
        $success = $this->installer->install($this->createOptions($input, $output));
107
108
        if ($success) {
109
            $this->success('CKEditor has been successfully installed...', $output);
110
        } else {
111
            $this->info('CKEditor installation has been skipped...', $output);
112
        }
113
    }
114
115
    /**
116
     * @param InputInterface  $input
117
     * @param OutputInterface $output
118
     *
119
     * @return mixed[]
120
     */
121
    private function createOptions(InputInterface $input, OutputInterface $output)
122
    {
123
        $options = ['notifier' => $this->createNotifier($input, $output)];
124
125
        if ($input->hasArgument('path')) {
126
            $options['path'] = $input->getArgument('path');
127
        }
128
129
        if ($input->hasOption('release')) {
130
            $options['release'] = $input->getOption('release');
131
        }
132
133
        if ($input->hasOption('tag')) {
134
            $options['version'] = $input->getOption('tag');
135
        }
136
137
        if ($input->hasOption('exclude')) {
138
            $options['excludes'] = $input->getOption('exclude');
139
        }
140
141
        if ($input->hasOption('clear')) {
142
            $options['clear'] = $input->getOption('clear');
143
        }
144
145
        return $options;
146
    }
147
148
    /**
149
     * @param InputInterface  $input
150
     * @param OutputInterface $output
151
     *
152
     * @return \Closure
153
     */
154
    private function createNotifier(InputInterface $input, OutputInterface $output)
155
    {
156
        $clear = $this->createProgressBar($output);
157
        $download = $this->createProgressBar($output);
158
        $extract = $this->createProgressBar($output);
159
160
        return function ($type, $data) use ($input, $output, $clear, $download, $extract) {
161
            switch ($type) {
162
                case CKEditorInstaller::NOTIFY_CLEAR:
163
                    $result = $this->choice(
164
                        [
165
                            sprintf('CKEditor is already installed in "%s"...', $data),
166
                            '',
167
                            'What do you want to do?',
168
                        ],
169
                        $choices = [
170
                            CKEditorInstaller::CLEAR_DROP => 'Drop the directory & reinstall CKEditor',
171
                            CKEditorInstaller::CLEAR_KEEP => 'Keep the directory & reinstall CKEditor by overriding files',
172
                            CKEditorInstaller::CLEAR_SKIP => 'Skip installation',
173
                        ],
174
                        CKEditorInstaller::CLEAR_DROP,
175
                        $input,
176
                        $output
177
                    );
178
179
                    if (($key = array_search($result, $choices, true)) !== false) {
180
                        $result = $key;
181
                    }
182
183
                    if ($result === CKEditorInstaller::CLEAR_DROP) {
184
                        $this->comment(sprintf('Dropping CKEditor from "%s"', $data), $output);
185
                    }
186
187
                    return $result;
188
189
                case CKEditorInstaller::NOTIFY_CLEAR_ARCHIVE:
190
                    $this->comment(sprintf('Dropping CKEditor ZIP archive "%s"', $data), $output);
191
                    break;
192
193
                case CKEditorInstaller::NOTIFY_CLEAR_COMPLETE:
194
                    $this->finishProgressBar($clear, $output);
195
                    break;
196
197
                case CKEditorInstaller::NOTIFY_CLEAR_PROGRESS:
198
                    $clear->advance();
199
                    break;
200
201
                case CKEditorInstaller::NOTIFY_CLEAR_SIZE:
202
                    $this->startProgressBar($clear, $output, $data);
203
                    break;
204
205
                case CKEditorInstaller::NOTIFY_DOWNLOAD:
206
                    $this->comment(sprintf('Downloading CKEditor ZIP archive from "%s"', $data), $output);
207
                    break;
208
209
                case CKEditorInstaller::NOTIFY_DOWNLOAD_COMPLETE:
210
                    $this->finishProgressBar($download, $output);
211
                    break;
212
213
                case CKEditorInstaller::NOTIFY_DOWNLOAD_PROGRESS:
214
                    $this->advanceProgressBar($download, $data);
215
                    break;
216
217
                case CKEditorInstaller::NOTIFY_DOWNLOAD_SIZE:
218
                    $this->startProgressBar($download, $output, $data);
219
                    break;
220
221
                case CKEditorInstaller::NOTIFY_EXTRACT:
222
                    $this->comment(sprintf('Extracting CKEditor ZIP archive to "%s"', $data), $output);
223
                    break;
224
225
                case CKEditorInstaller::NOTIFY_EXTRACT_COMPLETE:
226
                    $this->finishProgressBar($extract, $output);
227
                    break;
228
229
                case CKEditorInstaller::NOTIFY_EXTRACT_PROGRESS:
230
                    $extract->advance();
231
                    break;
232
233
                case CKEditorInstaller::NOTIFY_EXTRACT_SIZE:
234
                    $this->startProgressBar($extract, $output, $data);
235
                    break;
236
            }
237
        };
238
    }
239
240
    /**
241
     * @param OutputInterface $output
242
     */
243
    private function title(OutputInterface $output)
244
    {
245
        $output->writeln([
246
            '----------------------',
247
            '| CKEditor Installer |',
248
            '----------------------',
249
            '',
250
        ]);
251
    }
252
253
    /**
254
     * @param string|string[] $message
255
     * @param OutputInterface $output
256
     */
257
    private function comment($message, OutputInterface $output)
258
    {
259
        $output->writeln(' // '.$message);
260
        $output->writeln('');
261
    }
262
263
    /**
264
     * @param string          $message
265
     * @param OutputInterface $output
266
     */
267
    private function success($message, OutputInterface $output)
268
    {
269
        $this->block('[OK] - '.$message, $output, 'green', 'black');
270
    }
271
272
    /**
273
     * @param string          $message
274
     * @param OutputInterface $output
275
     */
276
    private function info($message, OutputInterface $output)
277
    {
278
        $this->block('[INFO] - '.$message, $output, 'orange', 'black');
279
    }
280
281
    /**
282
     * @param string          $message
283
     * @param OutputInterface $output
284
     * @param string          $background
285
     * @param string          $font
286
     */
287
    private function block($message, OutputInterface $output, $background = null, $font = null)
288
    {
289
        $options = [];
290
291
        if ($background !== null) {
292
            $options[] = 'bg='.$background;
293
        }
294
295
        if ($font !== null) {
296
            $options[] = 'fg='.$font;
297
        }
298
299
        $pattern = ' %s ';
300
301
        if (!empty($options)) {
302
            $pattern = '<'.implode(';', $options).'>'.$pattern.'</>';
303
        }
304
305
        $output->writeln($block = sprintf($pattern, str_repeat(' ', strlen($message))));
306
        $output->writeln(sprintf($pattern, $message));
307
        $output->writeln($block);
308
    }
309
310
    /**
311
     * @param string|string[] $question
312
     * @param string[]        $choices
313
     * @param string          $default
314
     * @param InputInterface  $input
315
     * @param OutputInterface $output
316
     *
317
     * @return string|null
318
     */
319
    private function choice($question, array $choices, $default, InputInterface $input, OutputInterface $output)
320
    {
321
        $helper = new QuestionHelper();
322
323
        $result = $helper->ask($input, $output, new ChoiceQuestion(
324
            $question,
0 ignored issues
show
Bug introduced by
It seems like $question defined by parameter $question on line 319 can also be of type array<integer,string>; however, Symfony\Component\Consol...Question::__construct() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
325
            $choices,
326
            $choices[$default]
327
        ));
328
329
        $output->writeln('');
330
331
        return $result;
332
    }
333
334
    /**
335
     * @param OutputInterface $output
336
     *
337
     * @return ProgressBar|ProgressHelper
338
     */
339
    private function createProgressBar(OutputInterface $output)
340
    {
341
        return class_exists(ProgressBar::class) ? new ProgressBar($output) : new ProgressHelper();
342
    }
343
344
    /**
345
     * @param ProgressBar|ProgressHelper $progress
346
     * @param OutputInterface            $output
347
     * @param int|null                   $max
348
     */
349
    private function startProgressBar($progress, OutputInterface $output, $max = null)
350
    {
351
        class_exists(ProgressBar::class) ? $progress->start($max) : $progress->start($output, $max);
0 ignored issues
show
Documentation introduced by
$output is of type object<Symfony\Component...Output\OutputInterface>, but the function expects a integer|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Unused Code introduced by
The call to ProgressBar::start() has too many arguments starting with $max.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
352
    }
353
354
    /**
355
     * @param ProgressBar|ProgressHelper $progress
356
     * @param int                        $current
357
     */
358
    private function advanceProgressBar($progress, $current)
359
    {
360
        class_exists(ProgressBar::class) ? $progress->setProgress($current) : $progress->setCurrent($current);
0 ignored issues
show
Bug introduced by
The method setCurrent() does not seem to exist on object<Symfony\Component...ole\Helper\ProgressBar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
361
    }
362
363
    /**
364
     * @param ProgressBar|ProgressHelper $progress
365
     * @param OutputInterface            $output
366
     */
367
    private function finishProgressBar($progress, OutputInterface $output)
368
    {
369
        $progress->finish();
370
        $output->writeln(['', '']);
371
    }
372
}
373