Completed
Pull Request — master (#298)
by Eric
64:58 queued 62:45
created

CKEditorInstallerCommand::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
crap 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 11
    public function __construct(CKEditorInstaller $installer = null)
39
    {
40 11
        parent::__construct();
41
42 11
        $this->installer = $installer ?: new CKEditorInstaller();
43 11
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48 11
    protected function configure()
49
    {
50 10
        $this
51 11
            ->setName('ckeditor:install')
52 11
            ->setDescription('Install CKEditor')
53 11
            ->addArgument('path', InputArgument::OPTIONAL, 'Where to install CKEditor')
54 11
            ->addOption('release', null, InputOption::VALUE_OPTIONAL, 'CKEditor release (basic, standard or full)')
55 11
            ->addOption('tag', null, InputOption::VALUE_OPTIONAL, 'CKEditor tag (x.y.z or latest)')
56 11
            ->addOption(
57 11
                'clear',
58 11
                null,
59 11
                InputOption::VALUE_OPTIONAL,
60 1
                'How to clear previous CKEditor installation (drop, keep or abort)'
61 10
            )
62 11
            ->addOption(
63 11
                'exclude',
64 11
                null,
65 11
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
66 1
                'Path to exclude when extracting CKEditor'
67 10
            )
68 11
            ->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=abort</info>
91
  
92
You can exclude path(s) when extracting CKEditor:
93
94 1
  <info>php %command.full_name% --exclude=samples --exclude=adapters</info>
95
EOF
96 10
            );
97 11
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102 11
    protected function execute(InputInterface $input, OutputInterface $output)
103
    {
104 11
        $this->title($output);
105
106 11
        $success = $this->installer->install($this->createOptions($input, $output));
107
108 11
        if ($success) {
109 11
            $this->success('CKEditor has been successfully installed...', $output);
110 10
        }
111 11
    }
112
113
    /**
114
     * @param InputInterface  $input
115
     * @param OutputInterface $output
116
     *
117
     * @return mixed[]
118
     */
119 11
    private function createOptions(InputInterface $input, OutputInterface $output)
120
    {
121 11
        $options = ['notifier' => $this->createNotifier($input, $output)];
122
123 11
        if ($input->hasArgument('path')) {
124 11
            $options['path'] = $input->getArgument('path');
125 10
        }
126
127 11
        if ($input->hasOption('release')) {
128 11
            $options['release'] = $input->getOption('release');
129 10
        }
130
131 11
        if ($input->hasOption('tag')) {
132 11
            $options['version'] = $input->getOption('tag');
133 10
        }
134
135 11
        if ($input->hasOption('exclude')) {
136 11
            $options['excludes'] = $input->getOption('exclude');
137 10
        }
138
139 11
        if ($input->hasOption('clear')) {
140 11
            $options['clear'] = $input->getOption('clear');
141 10
        }
142
143 11
        return $options;
144
    }
145
146
    /**
147
     * @param InputInterface  $input
148
     * @param OutputInterface $output
149
     *
150
     * @return \Closure
151
     */
152 11
    private function createNotifier(InputInterface $input, OutputInterface $output)
153
    {
154 11
        $clear = $this->createProgressBar($output);
155 11
        $download = $this->createProgressBar($output);
156 11
        $extract = $this->createProgressBar($output);
157
158 11
        return function ($type, $data) use ($input, $output, $clear, $download, $extract) {
159
            switch ($type) {
160 11
                case CKEditorInstaller::NOTIFY_CLEAR:
161 2
                    $result = $this->choice(
162
                        [
163 2
                            sprintf('CKEditor is already installed in "%s"...', $data),
164 2
                            '',
165 2
                            'What do you want to do?',
166 2
                        ],
167
                        $choices = [
168 2
                            CKEditorInstaller::CLEAR_DROP  => 'Drop the directory & reinstall CKEditor',
169 2
                            CKEditorInstaller::CLEAR_KEEP  => 'Keep the directory & reinstall CKEditor by overriding files',
170 2
                            CKEditorInstaller::CLEAR_ABORT => 'Abort installation',
171 2
                        ],
172 2
                        CKEditorInstaller::CLEAR_DROP,
173 2
                        $input,
174
                        $output
175 2
                    );
176
177 2
                    if (($key = array_search($result, $choices, true)) !== false) {
178
                        $result = $key;
179
                    }
180
181 2
                    if ($result === CKEditorInstaller::CLEAR_DROP) {
182 2
                        $this->comment(sprintf('Dropping CKEditor from "%s"', $data), $output);
183 2
                    }
184
185 2
                    return $result;
186
187 11
                case CKEditorInstaller::NOTIFY_CLEAR_ARCHIVE:
188 11
                    $this->comment(sprintf('Dropping CKEditor ZIP archive "%s"', $data), $output);
189 11
                    break;
190
191 11
                case CKEditorInstaller::NOTIFY_CLEAR_COMPLETE:
192 2
                    $this->finishProgressBar($clear, $output);
193 2
                    break;
194
195 11
                case CKEditorInstaller::NOTIFY_CLEAR_PROGRESS:
196 2
                    $clear->advance();
197 2
                    break;
198
199 11
                case CKEditorInstaller::NOTIFY_CLEAR_SIZE:
200 2
                    $this->startProgressBar($clear, $output, $data);
201 2
                    break;
202
203 11
                case CKEditorInstaller::NOTIFY_DOWNLOAD:
204 11
                    $this->comment(sprintf('Downloading CKEditor ZIP archive from "%s"', $data), $output);
205 11
                    break;
206
207 11
                case CKEditorInstaller::NOTIFY_DOWNLOAD_COMPLETE:
208 11
                    $this->finishProgressBar($download, $output);
209 11
                    break;
210
211 11
                case CKEditorInstaller::NOTIFY_DOWNLOAD_PROGRESS:
212 11
                    $this->advanceProgressBar($download, $data);
213 11
                    break;
214
215 11
                case CKEditorInstaller::NOTIFY_DOWNLOAD_SIZE:
216 11
                    $this->startProgressBar($download, $output, $data);
217 11
                    break;
218
219 11
                case CKEditorInstaller::NOTIFY_EXTRACT:
220 11
                    $this->comment(sprintf('Extracting CKEditor ZIP archive to "%s"', $data), $output);
221 11
                    break;
222
223 11
                case CKEditorInstaller::NOTIFY_EXTRACT_COMPLETE:
224 11
                    $this->finishProgressBar($extract, $output);
225 11
                    break;
226
227 11
                case CKEditorInstaller::NOTIFY_EXTRACT_PROGRESS:
228 11
                    $extract->advance();
229 11
                    break;
230
231 11
                case CKEditorInstaller::NOTIFY_EXTRACT_SIZE:
232 11
                    $this->startProgressBar($extract, $output, $data);
233 11
                    break;
234
            }
235 11
        };
236
    }
237
238
    /**
239
     * @param OutputInterface $output
240
     */
241 11
    private function title(OutputInterface $output)
242
    {
243 11
        $output->writeln([
244 11
            '----------------------',
245 10
            '| CKEditor Installer |',
246 10
            '----------------------',
247 10
            '',
248 10
        ]);
249 11
    }
250
251
    /**
252
     * @param string|string[] $message
253
     * @param OutputInterface $output
254
     */
255 11
    private function comment($message, OutputInterface $output)
256
    {
257 11
        $output->writeln(' // '.$message);
258 11
        $output->writeln('');
259 11
    }
260
261
    /**
262
     * @param string          $message
263
     * @param OutputInterface $output
264
     */
265 11
    private function success($message, OutputInterface $output)
266
    {
267 11
        $this->block('[OK] - '.$message, $output, 'green', 'black');
268 11
    }
269
270
    /**
271
     * @param string          $message
272
     * @param OutputInterface $output
273
     * @param string          $background
274
     * @param string          $font
275
     */
276 11
    private function block($message, OutputInterface $output, $background = null, $font = null)
277
    {
278 11
        $options = [];
279
280 11
        if ($background !== null) {
281 11
            $options[] = 'bg='.$background;
282 10
        }
283
284 11
        if ($font !== null) {
285 11
            $options[] = 'fg='.$font;
286 10
        }
287
288 11
        $pattern = ' %s ';
289
290 11
        if (!empty($options)) {
291 11
            $pattern = '<'.implode(';', $options).'>'.$pattern.'</>';
292 10
        }
293
294 11
        $output->writeln($block = sprintf($pattern, str_repeat(' ', strlen($message))));
295 11
        $output->writeln(sprintf($pattern, $message));
296 11
        $output->writeln($block);
297 11
    }
298
299
    /**
300
     * @param string|string[] $question
301
     * @param string[]        $choices
302
     * @param string          $default
303
     * @param InputInterface  $input
304
     * @param OutputInterface $output
305
     *
306
     * @return string|null
307
     */
308 2
    private function choice($question, array $choices, $default, InputInterface $input, OutputInterface $output)
309
    {
310 2
        $helper = new QuestionHelper();
311
312 2
        $result = $helper->ask($input, $output, new ChoiceQuestion(
313 2
            $question,
0 ignored issues
show
Bug introduced by
It seems like $question defined by parameter $question on line 308 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...
314 2
            $choices,
315 2
            $choices[$default]
316 2
        ));
317
318 2
        $output->writeln('');
319
320 2
        return $result;
321
    }
322
323
    /**
324
     * @param OutputInterface $output
325
     *
326
     * @return ProgressBar|ProgressHelper
327
     */
328 11
    private function createProgressBar(OutputInterface $output)
329
    {
330 11
        return class_exists(ProgressBar::class) ? new ProgressBar($output) : new ProgressHelper();
331
    }
332
333
    /**
334
     * @param ProgressBar|ProgressHelper $progress
335
     * @param OutputInterface            $output
336
     * @param int|null                   $max
337
     */
338 11
    private function startProgressBar($progress, OutputInterface $output, $max = null)
339
    {
340 11
        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...
341 11
    }
342
343
    /**
344
     * @param ProgressBar|ProgressHelper $progress
345
     * @param int                        $current
346
     */
347 11
    private function advanceProgressBar($progress, $current)
348
    {
349 11
        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...
350 11
    }
351
352
    /**
353
     * @param ProgressBar|ProgressHelper $progress
354
     * @param OutputInterface            $output
355
     */
356 11
    private function finishProgressBar($progress, OutputInterface $output)
357
    {
358 11
        $progress->finish();
359 11
        $output->writeln(['', '']);
360 11
    }
361
}
362