Completed
Push — develop ( 21139f...eaa140 )
by Tom
12s
created

InstallCommand   F

Complexity

Total Complexity 118

Size/Duplication

Total Lines 989
Duplicated Lines 0.61 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 12
Bugs 4 Features 0
Metric Value
wmc 118
c 12
b 4
f 0
lcom 1
cbo 14
dl 6
loc 989
rs 1.5999

21 Methods

Rating   Name   Duplication   Size   Complexity  
B downloadMagento() 0 45 5
A patchMagentoInstallerForPHP54() 0 10 2
F createDatabase() 0 92 13
B validateDatabaseSettings() 0 30 5
D installSampleData() 6 93 13
A _fixComposerExtractionBug() 0 15 3
A removeEmptyFolders() 0 11 3
B configure() 0 82 2
B execute() 0 35 6
A precheckPhp() 0 16 4
C selectMagentoVersion() 0 55 10
A getPackageNumberByName() 0 17 4
F installMagento() 0 217 28
A getInstallScriptPath() 0 10 2
A replaceHtaccessFile() 0 10 1
B runInstallScriptCommand() 0 37 5
A isEnabled() 0 4 1
A getTargetFolderByType() 0 21 2
B setDirectoryPermissions() 0 32 6
A getCliArguments() 0 8 2
A setCliArguments() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like InstallCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InstallCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace N98\Magento\Command\Installer;
4
5
use Composer\Composer;
6
use Composer\Package\CompletePackage;
7
use Exception;
8
use InvalidArgumentException;
9
use N98\Magento\Command\AbstractMagentoCommand;
10
use N98\Util\BinaryString;
11
use N98\Util\Database as DatabaseUtils;
12
use N98\Util\Exec;
13
use N98\Util\Filesystem;
14
use N98\Util\OperatingSystem;
15
use N98\Util\VerifyOrDie;
16
use PDO;
17
use PDOException;
18
use RuntimeException;
19
use Symfony\Component\Console\Helper\DialogHelper;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Input\StringInput;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Finder\Finder;
25
26
/**
27
 * Class InstallCommand
28
 *
29
 * @codeCoverageIgnore  - Travis server uses installer to create a new shop. If it not works complete build fails.
30
 * @package N98\Magento\Command\Installer
31
 */
32
class InstallCommand extends AbstractMagentoCommand
33
{
34
    /**
35
     * @deprecated since since 1.97.22; Use constant from Exec-Utility instead
36
     * @see Exec::CODE_CLEAN_EXIT
37
     */
38
    const EXEC_STATUS_OK = 0;
39
40
    const DEFAULT_SESSION_PATH = 'var/session';
41
42
    const MAGENTO_INSTALL_SCRIPT_PATH = 'install.php';
43
44
    /**
45
     * @var array
46
     */
47
    protected $config;
48
49
    /**
50
     * @var array
51
     */
52
    protected $_argv;
53
54
    /**
55
     * @var array
56
     */
57
    protected $commandConfig;
58
59
    /**
60
     * @var \Closure
61
     */
62
    protected $notEmptyCallback;
63
64
    protected function configure()
65
    {
66
        $this
67
            ->setName('install')
68
            ->addOption('magentoVersion', null, InputOption::VALUE_OPTIONAL, 'Magento version')
69
            ->addOption(
70
                'magentoVersionByName',
71
                null,
72
                InputOption::VALUE_OPTIONAL,
73
                'Magento version name instead of order number'
74
            )
75
            ->addOption('installationFolder', null, InputOption::VALUE_OPTIONAL, 'Installation folder')
76
            ->addOption('dbHost', null, InputOption::VALUE_OPTIONAL, 'Database host')
77
            ->addOption('dbUser', null, InputOption::VALUE_OPTIONAL, 'Database user')
78
            ->addOption('dbPass', null, InputOption::VALUE_OPTIONAL, 'Database password')
79
            ->addOption('dbName', null, InputOption::VALUE_OPTIONAL, 'Database name')
80
            ->addOption('dbPort', null, InputOption::VALUE_OPTIONAL, 'Database port', 3306)
81
            ->addOption('dbPrefix', null, InputOption::VALUE_OPTIONAL, 'Table prefix', '')
82
            ->addOption('installSampleData', null, InputOption::VALUE_OPTIONAL, 'Install sample data')
83
            ->addOption(
84
                'useDefaultConfigParams',
85
                null,
86
                InputOption::VALUE_OPTIONAL,
87
                'Use default installation parameters defined in the yaml file'
88
            )->addOption('baseUrl', null, InputOption::VALUE_OPTIONAL, 'Installation base url')
89
            ->addOption(
90
                'replaceHtaccessFile',
91
                null,
92
                InputOption::VALUE_OPTIONAL,
93
                'Generate htaccess file (for non vhost environment)'
94
            )->addOption(
95
                'noDownload',
96
                null,
97
                InputOption::VALUE_NONE,
98
                'If set skips download step. Used when installationFolder is already a Magento installation that has ' .
99
                'to be installed on the given database.'
100
            )
101
            ->addOption(
102
                'only-download',
103
                null,
104
                InputOption::VALUE_NONE,
105
                'Downloads (and extracts) source code'
106
            )
107
            ->addOption(
108
                'forceUseDb',
109
                null,
110
                InputOption::VALUE_NONE,
111
                'If --noDownload passed, force to use given database if it already exists.'
112
            )->setDescription('Install magento');
113
114
        $help = <<<HELP
115
* Download Magento by a list of git repos and zip files (mageplus, 
116
  magelte, official community packages).
117
* Try to create database if it does not exist.
118
* Installs Magento sample data if available (since version 1.2.0).
119
* Starts Magento installer
120
* Sets rewrite base in .htaccess file
121
122
Example of an unattended Magento CE 1.7.0.2 installation:
123
124
   $ n98-magerun.phar install --dbHost="localhost" --dbUser="mydbuser" \
125
     --dbPass="mysecret" --dbName="magentodb" --installSampleData=yes \
126
     --useDefaultConfigParams=yes \
127
     --magentoVersionByName="magento-ce-1.7.0.2" \
128
     --installationFolder="magento" --baseUrl="http://magento.localdomain/"
129
130
Additionally, with --noDownload option you can install Magento working 
131
copy already stored in --installationFolder on the given database.
132
133
See it in action: http://youtu.be/WU-CbJ86eQc
134
135
HELP;
136
        $this->setHelp($help);
137
138
        $this->notEmptyCallback = function ($input) {
139
            if (empty($input)) {
140
                throw new InvalidArgumentException('Please enter a value');
141
            }
142
143
            return $input;
144
        };
145
    }
146
147
    /**
148
     * @return bool
149
     */
150
    public function isEnabled()
151
    {
152
        return Exec::allowed();
153
    }
154
155
    /**
156
     * @param InputInterface $input
157
     * @param OutputInterface $output
158
     * @throws RuntimeException
159
     * @return int|null|void
160
     */
161
    protected function execute(InputInterface $input, OutputInterface $output)
162
    {
163
        $this->commandConfig = $this->getCommandConfig();
164
        $this->writeSection($output, 'Magento Installation');
165
166
        $this->precheckPhp();
167
168
        if (!$input->getOption('noDownload')) {
169
            $this->selectMagentoVersion($input, $output);
170
        }
171
172
        $this->chooseInstallationFolder($input, $output);
173
174
        if (!$input->getOption('noDownload')) {
175
            $result = $this->downloadMagento($input, $output);
176
177
            if ($result === false) {
178
                return 1;
179
            }
180
        }
181
182
        if ($input->getOption('only-download')) {
183
            return 0;
184
        }
185
186
        $this->createDatabase($input, $output);
187
188
        if (!$input->getOption('noDownload')) {
189
            $this->installSampleData($input, $output);
190
        }
191
192
        $this->removeEmptyFolders();
193
        $this->setDirectoryPermissions($output);
194
        $this->installMagento($input, $output, $this->config['installationFolder']);
0 ignored issues
show
Unused Code introduced by
The call to InstallCommand::installMagento() has too many arguments starting with $this->config['installationFolder'].

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...
195
    }
196
197
    /**
198
     * Check PHP environment agains minimal required settings modules
199
     */
200
    protected function precheckPhp()
201
    {
202
        $extensions = $this->commandConfig['installation']['pre-check']['php']['extensions'];
203
        $missingExtensions = array();
204
        foreach ($extensions as $extension) {
205
            if (!extension_loaded($extension)) {
206
                $missingExtensions[] = $extension;
207
            }
208
        }
209
210
        if (count($missingExtensions) > 0) {
211
            throw new RuntimeException(
212
                'The following PHP extensions are required to start installation: ' . implode(',', $missingExtensions)
213
            );
214
        }
215
    }
216
217
    /**
218
     * @param InputInterface $input
219
     * @param OutputInterface $output
220
     *
221
     * @throws InvalidArgumentException
222
     */
223
    protected function selectMagentoVersion(InputInterface $input, OutputInterface $output)
224
    {
225
        if ($input->getOption('magentoVersion') == null && $input->getOption('magentoVersionByName') == null) {
226
            $question = array();
227
            foreach ($this->commandConfig['magento-packages'] as $key => $package) {
228
                $question[] = '<comment>' . str_pad('[' . ($key + 1) . ']', 4, ' ') . '</comment> ' .
229
                    $package['name'] . "\n";
230
            }
231
            $question[] = "<question>Choose a magento version:</question> ";
232
233
            $commandConfig = $this->commandConfig;
234
235
            $type = $this->getHelper('dialog')->askAndValidate(
236
                $output,
237
                $question,
238
                function ($typeInput) use ($commandConfig) {
239
                    if (!in_array($typeInput, range(1, count($commandConfig['magento-packages'])))) {
240
                        throw new InvalidArgumentException('Invalid type');
241
                    }
242
243
                    return $typeInput;
244
                }
245
            );
246
        } else {
247
            $type = null;
248
249
            if ($input->getOption('magentoVersion')) {
250
                $type = $input->getOption('magentoVersion');
251
                if ($type !== (string) (int) $type) {
252
                    $type = $this->getPackageNumberByName($type);
253
                }
254
            } elseif ($input->getOption('magentoVersionByName')) {
255
                $type = $this->getPackageNumberByName($input->getOption('magentoVersionByName'));
256
            }
257
258
            if ($type == null) {
259
                throw new InvalidArgumentException('Unable to locate Magento version');
260
            }
261
        }
262
263
        $magentoPackages = $this->commandConfig['magento-packages'];
264
265
        $index = $type - 1;
266
        if (!isset($magentoPackages[$index])) {
267
            throw new InvalidArgumentException(
268
                sprintf(
269
                    'Invalid Magento package number %s, must be from 1 to %d.',
270
                    var_export($type, true),
271
                    count($magentoPackages)
272
                )
273
            );
274
        }
275
276
        $this->config['magentoVersionData'] = $magentoPackages[$index];
277
    }
278
279
    /**
280
     * @param $name
281
     *
282
     * @return int 1 or greater as the one-based package number, null on failure to resolve the name
283
     */
284
    private function getPackageNumberByName($name)
285
    {
286
        // directly filter integer strings
287
        if ($name === (string) (int) $name) {
288
            return (int) $name;
289
        }
290
291
        $magentoPackages = $this->commandConfig['magento-packages'];
292
293
        foreach ($magentoPackages as $key => $package) {
294
            if ($package['name'] === $name) {
295
                return $key + 1;
296
            }
297
        }
298
299
        return;
300
    }
301
302
    /**
303
     * @param InputInterface $input
304
     * @param OutputInterface $output
305
     * @return bool
306
     */
307
    public function downloadMagento(InputInterface $input, OutputInterface $output)
308
    {
309
        try {
310
            $package = $this->createComposerPackageByConfig($this->config['magentoVersionData']);
311
            $this->config['magentoPackage'] = $package;
312
313
            if (file_exists($this->config['installationFolder'] . '/app/Mage.php')) {
314
                $output->writeln('<error>A magento installation already exists in this folder </error>');
315
316
                return false;
317
            }
318
319
            $composer = $this->getComposer($input, $output);
320
            $targetFolder = $this->getTargetFolderByType($composer, $package, $this->config['installationFolder']);
321
            $this->config['magentoPackage'] = $this->downloadByComposerConfig(
322
                $input,
323
                $output,
324
                $package,
325
                $targetFolder,
326
                true
327
            );
328
329
            if ($this->isSourceTypeRepository($package->getSourceType())) {
330
                $filesystem = new \N98\Util\Filesystem;
331
                $filesystem->recursiveCopy($targetFolder, $this->config['installationFolder'], array('.git', '.hg'));
332
            } else {
333
                $filesystem = new \Composer\Util\Filesystem();
334
                $filesystem->copyThenRemove(
335
                    $this->config['installationFolder'] . '/_n98_magerun_download',
336
                    $this->config['installationFolder']
337
                );
338
            }
339
340
            if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
341
                // Patch installer
342
                $this->patchMagentoInstallerForPHP54($this->config['installationFolder']);
343
            }
344
        } catch (Exception $e) {
345
            $output->writeln('<error>' . $e->getMessage() . '</error>');
346
347
            return false;
348
        }
349
350
        return true;
351
    }
352
353
    /**
354
     * construct a folder to where magerun will download the source to, cache git/hg repositories under COMPOSER_HOME
355
     *
356
     * @param Composer $composer
357
     * @param CompletePackage $package
358
     * @param $installationFolder
359
     *
360
     * @return string
361
     */
362
    protected function getTargetFolderByType(Composer $composer, CompletePackage $package, $installationFolder)
363
    {
364
        $type = $package->getSourceType();
365
        if ($this->isSourceTypeRepository($type)) {
366
            $targetPath = sprintf(
367
                '%s/%s/%s/%s',
368
                $composer->getConfig()->get('cache-dir'),
369
                '_n98_magerun_download',
370
                $type,
371
                preg_replace('{[^a-z0-9.]}i', '-', $package->getSourceUrl())
372
            );
373
        } else {
374
            $targetPath = sprintf(
375
                '%s/%s',
376
                $installationFolder,
377
                '_n98_magerun_download'
378
            );
379
        }
380
381
        return $targetPath;
382
    }
383
384
    /**
385
     * @param string $magentoFolder
386
     */
387
    protected function patchMagentoInstallerForPHP54($magentoFolder)
388
    {
389
        $installerConfig = $magentoFolder
390
            . DIRECTORY_SEPARATOR
391
            . 'app/code/core/Mage/Install/etc/config.xml';
392
        if (file_exists($installerConfig)) {
393
            $xml = file_get_contents($installerConfig);
394
            file_put_contents($installerConfig, str_replace('<pdo_mysql/>', '<pdo_mysql>1</pdo_mysql>', $xml));
395
        }
396
    }
397
398
    /**
399
     * @param InputInterface $input
400
     * @param OutputInterface $output
401
     *
402
     * @throws InvalidArgumentException
403
     */
404
    protected function createDatabase(InputInterface $input, OutputInterface $output)
405
    {
406
        $dbOptions = array('--dbHost', '--dbUser', '--dbPass', '--dbName');
407
        $dbOptionsFound = 0;
408
        foreach ($dbOptions as $dbOption) {
409
            foreach ($this->getCliArguments() as $definedCliOption) {
410
                if (BinaryString::startsWith($definedCliOption, $dbOption)) {
411
                    $dbOptionsFound++;
412
                }
413
            }
414
        }
415
416
        $hasAllOptions = $dbOptionsFound == 4;
417
418
        // if all database options were passed in at cmd line
419
        if ($hasAllOptions) {
420
            $this->config['db_host'] = $input->getOption('dbHost');
421
            $this->config['db_user'] = $input->getOption('dbUser');
422
            $this->config['db_pass'] = $input->getOption('dbPass');
423
            $this->config['db_name'] = VerifyOrDie::filename(
424
                $input->getOption('dbName'),
425
                'Database name is not portable'
426
            );
427
            $this->config['db_port'] = $input->getOption('dbPort');
428
            $this->config['db_prefix'] = $input->getOption('dbPrefix');
429
            $db = $this->validateDatabaseSettings($output, $input);
430
431
            if ($db === false) {
432
                throw new InvalidArgumentException("Database configuration is invalid");
433
            }
434
        } else {
435
            /** @var DialogHelper $dialog */
436
            $dialog = $this->getHelper('dialog');
437
            do {
438
                $dbHostDefault = $input->getOption('dbHost') ? $input->getOption('dbHost') : 'localhost';
439
                $this->config['db_host'] = $dialog->askAndValidate(
440
                    $output,
441
                    '<question>Please enter the database host</question> <comment>[' . $dbHostDefault . ']</comment>: ',
442
                    $this->notEmptyCallback,
443
                    false,
444
                    $dbHostDefault
445
                );
446
447
                $dbUserDefault = $input->getOption('dbUser') ? $input->getOption('dbUser') : 'root';
448
                $this->config['db_user'] = $dialog->askAndValidate(
449
                    $output,
450
                    '<question>Please enter the database username</question> <comment>[' . $dbUserDefault .
451
                    ']</comment>: ',
452
                    $this->notEmptyCallback,
453
                    false,
454
                    $dbUserDefault
455
                );
456
457
                $dbPassDefault = $input->getOption('dbPass') ? $input->getOption('dbPass') : '';
458
                $this->config['db_pass'] = $dialog->ask(
459
                    $output,
460
                    '<question>Please enter the database password</question> <comment>[' . $dbPassDefault .
461
                    ']</comment>: ',
462
                    $dbPassDefault
463
                );
464
465
                $dbNameDefault = $input->getOption('dbName') ? $input->getOption('dbName') : 'magento';
466
                $this->config['db_name'] = $dialog->askAndValidate(
467
                    $output,
468
                    '<question>Please enter the database name</question> <comment>[' . $dbNameDefault . ']</comment>: ',
469
                    $this->notEmptyCallback,
470
                    false,
471
                    $dbNameDefault
472
                );
473
474
                $dbPortDefault = $input->getOption('dbPort') ? $input->getOption('dbPort') : 3306;
475
                $this->config['db_port'] = $dialog->askAndValidate(
476
                    $output,
477
                    '<question>Please enter the database port </question> <comment>[' . $dbPortDefault .
478
                    ']</comment>: ',
479
                    $this->notEmptyCallback,
480
                    false,
481
                    $dbPortDefault
482
                );
483
484
                $dbPrefixDefault = $input->getOption('dbPrefix') ? $input->getOption('dbPrefix') : '';
485
                $this->config['db_prefix'] = $dialog->ask(
486
                    $output,
487
                    '<question>Please enter the table prefix</question> <comment>[' . $dbPrefixDefault . ']</comment>:',
488
                    $dbPrefixDefault
489
                );
490
                $db = $this->validateDatabaseSettings($output, $input);
491
            } while ($db === false);
492
        }
493
494
        $this->config['db'] = $db;
495
    }
496
497
    /**
498
     * @param OutputInterface $output
499
     * @param InputInterface $input
500
     *
501
     * @return bool|PDO
502
     */
503
    protected function validateDatabaseSettings(OutputInterface $output, InputInterface $input)
504
    {
505
        try {
506
            $dsn = sprintf("mysql:host=%s;port=%s", $this->config['db_host'], $this->config['db_port']);
507
            $db = new PDO($dsn, $this->config['db_user'], $this->config['db_pass']);
508
            if (!$db->query('USE ' . $this->config['db_name'])) {
509
                $db->query("CREATE DATABASE `" . $this->config['db_name'] . "`");
510
                $output->writeln('<info>Created database ' . $this->config['db_name'] . '</info>');
511
                $db->query('USE ' . $this->config['db_name']);
512
513
                return $db;
514
            }
515
        } catch (PDOException $e) {
516
            $output->writeln('<error>' . $e->getMessage() . '</error>');
517
            return false;
518
        }
519
520
        if ($input->getOption('noDownload') && !$input->getOption('forceUseDb')) {
521
            $output->writeln(
522
                sprintf(
523
                    "<error>Database '%s' already exists, use --forceUseDb in combination with --noDownload" .
524
                    " to use an existing database</error>",
525
                    $this->config['db_name']
526
                )
527
            );
528
            return false;
529
        }
530
531
        return $db;
532
    }
533
534
    /**
535
     * @param InputInterface $input
536
     * @param OutputInterface $output
537
     */
538
    protected function installSampleData(InputInterface $input, OutputInterface $output)
539
    {
540
        $magentoPackage = $this->config['magentoPackage'];
541
        /* @var $magentoPackage \Composer\Package\MemoryPackage */
542
        $extra = $magentoPackage->getExtra();
543
        if (!isset($extra['sample-data'])) {
544
            return;
545
        }
546
547
        $dialog = $this->getHelper('dialog');
548
549
        $installSampleData = ($input->getOption('installSampleData') !== null)
550
            ? $this->_parseBoolOption($input->getOption('installSampleData'))
551
            : $dialog->askConfirmation(
552
                $output,
553
                '<question>Install sample data?</question> <comment>[y]</comment>: '
554
            );
555
556
        if ($installSampleData) {
557
            $filesystem = new Filesystem();
558
559
            foreach ($this->commandConfig['demo-data-packages'] as $demoPackageData) {
560
                if ($demoPackageData['name'] == $extra['sample-data']) {
561
                    $package = $this->downloadByComposerConfig(
562
                        $input,
563
                        $output,
564
                        $demoPackageData,
565
                        $this->config['installationFolder'] . '/_temp_demo_data',
566
                        false
567
                    );
568
569
                    $this->_fixComposerExtractionBug();
570
571
                    $expandedFolder = $this->config['installationFolder']
572
                        . '/_temp_demo_data/'
573
                        . str_replace(array('.tar.gz', '.tar.bz2', '.zip'), '', basename($package->getDistUrl()));
574
                    if (is_dir($expandedFolder)) {
575
                        $filesystem->recursiveCopy(
576
                            $expandedFolder,
577
                            $this->config['installationFolder']
578
                        );
579
                        $filesystem->recursiveRemoveDirectory($expandedFolder);
580
                    }
581
582
                    // Remove empty folder
583 View Code Duplication
                    if (is_dir($this->config['installationFolder'] . '/vendor/composer')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
584
                        $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/vendor/composer');
585
                    }
586
587
                    // Install sample data
588
                    $sampleDataSqlFile = glob(
589
                        $this->config['installationFolder'] . '/_temp_demo_data/magento_*sample_data*sql'
590
                    );
591
                    $db = $this->config['db'];
592
                    /* @var $db PDO */
593
                    if (isset($sampleDataSqlFile[0])) {
594
                        if (OperatingSystem::isProgramInstalled('mysql')) {
595
                            $exec = 'mysql '
596
                                . '-h' . escapeshellarg(strval($this->config['db_host']))
597
                                . ' '
598
                                . '-u' . escapeshellarg(strval($this->config['db_user']))
599
                                . ' '
600
                                . ($this->config['db_port'] != '3306'
601
                                    ? '-P' . escapeshellarg($this->config['db_port']) . ' ' : '')
602
                                . (!strval($this->config['db_pass'] == '')
603
                                    ? '-p' . escapeshellarg($this->config['db_pass']) . ' ' : '')
604
                                . strval($this->config['db_name'])
605
                                . ' < '
606
                                . escapeshellarg($sampleDataSqlFile[0]);
607
                            $output->writeln(
608
                                '<info>Importing <comment>' . $sampleDataSqlFile[0] .
609
                                '</comment> with mysql cli client</info>'
610
                            );
611
                            Exec::run($exec);
612
                            @unlink($sampleDataSqlFile[0]);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
613
                        } else {
614
                            $output->writeln(
615
                                '<info>Importing <comment>' . $sampleDataSqlFile[0] .
616
                                '</comment> with PDO driver</info>'
617
                            );
618
                            // Fallback -> Try to install dump file by PDO driver
619
                            $dbUtils = new DatabaseUtils();
620
                            $dbUtils->importSqlDump($db, $sampleDataSqlFile[0]);
621
                        }
622
                    }
623
                }
624
            }
625
626 View Code Duplication
            if (is_dir($this->config['installationFolder'] . '/_temp_demo_data')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
627
                $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/_temp_demo_data');
628
            }
629
        }
630
    }
631
632
    protected function _fixComposerExtractionBug()
633
    {
634
        $filesystem = new Filesystem();
635
        foreach (array('/_temp_demo_data/media' => '/media', '/_temp_demo_data/skin' => '/skin') as $wrong => $right) {
636
            $wrongFolder = $this->config['installationFolder'] . $wrong;
637
            $rightFolder = $this->config['installationFolder'] . $right;
638
            if (is_dir($wrongFolder)) {
639
                $filesystem->recursiveCopy(
640
                    $wrongFolder,
641
                    $rightFolder
642
                );
643
                $filesystem->recursiveRemoveDirectory($wrongFolder);
644
            }
645
        }
646
    }
647
648
    /**
649
     * Remove empty composer extraction folder
650
     */
651
    protected function removeEmptyFolders()
652
    {
653
        if (is_dir(getcwd() . '/vendor')) {
654
            $finder = new Finder();
655
            $finder->files()->depth(3)->in(getcwd() . '/vendor');
656
            if ($finder->count() == 0) {
657
                $filesystem = new Filesystem();
658
                $filesystem->recursiveRemoveDirectory(getcwd() . '/vendor');
659
            }
660
        }
661
    }
662
663
    /**
664
     * @param InputInterface $input
665
     * @param OutputInterface $output
666
     *
667
     * @return array
668
     * @throws InvalidArgumentException parameter mismatch (e.g. base-url components like hostname)
669
     * @throws RuntimeException
670
     */
671
    protected function installMagento(InputInterface $input, OutputInterface $output)
672
    {
673
        $this->getApplication()->setAutoExit(false);
674
        /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */
675
        $dialog = $this->getHelper('dialog');
676
677
        $defaults = $this->commandConfig['installation']['defaults'];
678
679
        $useDefaultConfigParams = $this->_parseBoolOption($input->getOption('useDefaultConfigParams'));
680
681
        $sessionSave = $useDefaultConfigParams ? $defaults['session_save'] : $dialog->ask(
682
            $output,
683
            '<question>Please enter the session save:</question> <comment>[' .
684
            $defaults['session_save'] . ']</comment>: ',
685
            $defaults['session_save']
686
        );
687
688
        $adminFrontname = $useDefaultConfigParams ? $defaults['admin_frontname'] : $dialog->askAndValidate(
689
            $output,
690
            '<question>Please enter the admin frontname:</question> <comment>[' .
691
            $defaults['admin_frontname'] . ']</comment> ',
692
            $this->notEmptyCallback,
693
            false,
694
            $defaults['admin_frontname']
695
        );
696
697
        $currency = $useDefaultConfigParams ? $defaults['currency'] : $dialog->askAndValidate(
698
            $output,
699
            '<question>Please enter the default currency code:</question> <comment>[' .
700
            $defaults['currency'] . ']</comment>: ',
701
            $this->notEmptyCallback,
702
            false,
703
            $defaults['currency']
704
        );
705
706
        $locale = $useDefaultConfigParams ? $defaults['locale'] : $dialog->askAndValidate(
707
            $output,
708
            '<question>Please enter the locale code:</question> <comment>[' . $defaults['locale'] . ']</comment>: ',
709
            $this->notEmptyCallback,
710
            false,
711
            $defaults['locale']
712
        );
713
714
        $timezone = $useDefaultConfigParams ? $defaults['timezone'] : $dialog->askAndValidate(
715
            $output,
716
            '<question>Please enter the timezone:</question> <comment>[' . $defaults['timezone'] . ']</comment>: ',
717
            $this->notEmptyCallback,
718
            false,
719
            $defaults['timezone']
720
        );
721
722
        $adminUsername = $useDefaultConfigParams ? $defaults['admin_username'] : $dialog->askAndValidate(
723
            $output,
724
            '<question>Please enter the admin username:</question> <comment>[' .
725
            $defaults['admin_username'] . ']</comment>: ',
726
            $this->notEmptyCallback,
727
            false,
728
            $defaults['admin_username']
729
        );
730
731
        $adminPassword = $useDefaultConfigParams ? $defaults['admin_password'] : $dialog->askAndValidate(
732
            $output,
733
            '<question>Please enter the admin password:</question> <comment>[' .
734
            $defaults['admin_password'] . ']</comment>: ',
735
            $this->notEmptyCallback,
736
            false,
737
            $defaults['admin_password']
738
        );
739
740
        $adminFirstname = $useDefaultConfigParams ? $defaults['admin_firstname'] : $dialog->askAndValidate(
741
            $output,
742
            '<question>Please enter the admin\'s firstname:</question> <comment>[' .
743
            $defaults['admin_firstname'] . ']</comment>: ',
744
            $this->notEmptyCallback,
745
            false,
746
            $defaults['admin_firstname']
747
        );
748
749
        $adminLastname = $useDefaultConfigParams ? $defaults['admin_lastname'] : $dialog->askAndValidate(
750
            $output,
751
            '<question>Please enter the admin\'s lastname:</question> <comment>[' .
752
            $defaults['admin_lastname'] . ']</comment>: ',
753
            $this->notEmptyCallback,
754
            false,
755
            $defaults['admin_lastname']
756
        );
757
758
        $adminEmail = $useDefaultConfigParams ? $defaults['admin_email'] : $dialog->askAndValidate(
759
            $output,
760
            '<question>Please enter the admin\'s email:</question> <comment>[' .
761
            $defaults['admin_email'] . ']</comment>: ',
762
            $this->notEmptyCallback,
763
            false,
764
            $defaults['admin_email']
765
        );
766
767
        $validateBaseUrl = function ($input) {
768
            if (!preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $input)) {
769
                throw new InvalidArgumentException(
770
                    sprintf('Invalid URL %s. Please enter a valid URL', var_export($input, true))
771
                );
772
            }
773
            if (parse_url($input, \PHP_URL_HOST) == 'localhost') {
774
                throw new InvalidArgumentException(
775
                    'localhost cause problems! Please use 127.0.0.1 or another hostname'
776
                );
777
            }
778
779
            return $input;
780
        };
781
782
        $baseUrl = $input->getOption('baseUrl');
783
        if (null === $baseUrl) {
784
            if (!$input->isInteractive()) {
785
                throw new InvalidArgumentException('Installation base url is mandatory, use --baseUrl.');
786
            }
787
            $baseUrl = $dialog->askAndValidate(
788
                $output,
789
                '<question>Please enter the base url:</question> ',
790
                $validateBaseUrl
791
            );
792
        }
793
        $validateBaseUrl($baseUrl);
794
        $baseUrl = rtrim($baseUrl, '/') . '/'; // normalize baseUrl
795
796
        /**
797
         * Correct session save (common mistake)
798
         */
799
        if ($sessionSave == 'file') {
800
            $sessionSave = 'files';
801
        }
802
803
        /**
804
         * Try to create session folder
805
         */
806
        $defaultSessionFolder = $this->config['installationFolder'] . '/' . self::DEFAULT_SESSION_PATH;
807
        if ($sessionSave == 'files' && !is_dir($defaultSessionFolder)) {
808
            @mkdir($defaultSessionFolder);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
809
        }
810
811
        $dbHost = $this->config['db_host'];
812
        if ($this->config['db_port'] != 3306) {
813
            $dbHost .= ':' . $this->config['db_port'];
814
        }
815
816
        $argv = array(
817
            'license_agreement_accepted' => 'yes',
818
            'locale'                     => $locale,
819
            'timezone'                   => $timezone,
820
            'db_host'                    => $dbHost,
821
            'db_name'                    => $this->config['db_name'],
822
            'db_user'                    => $this->config['db_user'],
823
            'db_pass'                    => $this->config['db_pass'],
824
            'db_prefix'                  => $this->config['db_prefix'],
825
            'url'                        => $baseUrl,
826
            'use_rewrites'               => 'yes',
827
            'use_secure'                 => 'no',
828
            'secure_base_url'            => '',
829
            'use_secure_admin'           => 'no',
830
            'admin_username'             => $adminUsername,
831
            'admin_lastname'             => $adminLastname,
832
            'admin_firstname'            => $adminFirstname,
833
            'admin_email'                => $adminEmail,
834
            'admin_password'             => $adminPassword,
835
            'session_save'               => $sessionSave,
836
            'admin_frontname'            => $adminFrontname, /* magento 1 */
837
            'backend_frontname'          => $adminFrontname, /* magento 2 */
838
            'default_currency'           => $currency,
839
            'skip_url_validation'        => 'yes',
840
        );
841
        if ($useDefaultConfigParams) {
842
            if (strlen($defaults['encryption_key']) > 0) {
843
                $argv['encryption_key'] = $defaults['encryption_key'];
844
            }
845
            if (strlen($defaults['use_secure']) > 0) {
846
                $argv['use_secure'] = $defaults['use_secure'];
847
                $argv['secure_base_url'] = str_replace('http://', 'https://', $baseUrl);
848
            }
849
            if (strlen($defaults['use_rewrites']) > 0) {
850
                $argv['use_rewrites'] = $defaults['use_rewrites'];
851
            }
852
        }
853
854
        $this->runInstallScriptCommand($output, $this->config['installationFolder'], $argv);
855
856
        /* @var $dialog DialogHelper */
857
        $dialog = $this->getHelper('dialog');
858
859
        /**
860
         * Htaccess file
861
         */
862
        if ($input->getOption('useDefaultConfigParams') == null || $input->getOption('replaceHtaccessFile') != null) {
863
            $replaceHtaccessFile = false;
864
865
            if ($this->_parseBoolOption($input->getOption('replaceHtaccessFile'))) {
866
                $replaceHtaccessFile = true;
867
            } elseif ($dialog->askConfirmation(
868
                $output,
869
                '<question>Write BaseURL to .htaccess file?</question> <comment>[n]</comment>: ',
870
                false
871
            )
872
            ) {
873
                $replaceHtaccessFile = true;
874
            }
875
876
            if ($replaceHtaccessFile) {
877
                $this->replaceHtaccessFile($baseUrl);
878
            }
879
        }
880
881
        \chdir($this->config['installationFolder']);
882
        $this->getApplication()->reinit();
883
        $output->writeln('<info>Reindex all after installation</info>');
884
        $this->getApplication()->run(new StringInput('index:reindex:all'), $output);
885
        $this->getApplication()->run(new StringInput('sys:check'), $output);
886
        $output->writeln('<info>Successfully installed magento</info>');
887
    }
888
889
    /**
890
     * Check if we have a magento 2 or 1 installation and return path to install.php
891
     *
892
     * @return string
893
     */
894
    protected function getInstallScriptPath()
895
    {
896
        $magento1InstallScriptPath = $this->config['installationFolder'] . '/' . MAGENTO_INSTALL_SCRIPT_PATH;
897
        $magento2InstallScriptPath = $this->config['installationFolder'] . '/dev/shell/install.php';
898
        if (file_exists($magento2InstallScriptPath)) {
899
            return $magento2InstallScriptPath;
900
        }
901
902
        return $magento1InstallScriptPath;
903
    }
904
905
    /**
906
     * @param string $baseUrl
907
     */
908
    protected function replaceHtaccessFile($baseUrl)
909
    {
910
        $content = file_get_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess');
911
        copy(
912
            $this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess',
913
            $this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess.dist'
914
        );
915
        $content = str_replace('#RewriteBase /magento/', 'RewriteBase ' . parse_url($baseUrl, PHP_URL_PATH), $content);
916
        file_put_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess', $content);
917
    }
918
919
    /**
920
     * @param OutputInterface $output
921
     */
922
    protected function setDirectoryPermissions($output)
923
    {
924
        try {
925
            $varFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var';
926
            if (!is_dir($varFolder)) {
927
                @mkdir($varFolder);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
928
            }
929
            @chmod($varFolder, 0777);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
930
931
            $varCacheFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/cache';
932
            if (!is_dir($varCacheFolder)) {
933
                @mkdir($varCacheFolder);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
934
            }
935
            @chmod($varCacheFolder, 0777);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
936
937
            $mediaFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'media';
938
            if (!is_dir($mediaFolder)) {
939
                @mkdir($mediaFolder);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
940
            }
941
            @chmod($mediaFolder, 0777);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
942
943
            $finder = Finder::create();
944
            $finder->directories()
945
                ->ignoreUnreadableDirs(true)
946
                ->in(array($varFolder, $mediaFolder));
947
            foreach ($finder as $dir) {
948
                @chmod($dir->getRealpath(), 0777);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
949
            }
950
        } catch (Exception $e) {
951
            $output->writeln('<error>' . $e->getMessage() . '</error>');
952
        }
953
    }
954
955
    /**
956
     * @return array
957
     */
958
    public function getCliArguments()
0 ignored issues
show
Coding Style introduced by
getCliArguments uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
959
    {
960
        if ($this->_argv === null) {
961
            $this->_argv = $_SERVER['argv'];
962
        }
963
964
        return $this->_argv;
965
    }
966
967
    /**
968
     * @param array $args
969
     */
970
    public function setCliArguments($args)
971
    {
972
        $this->_argv = $args;
973
    }
974
975
    /**
976
     * Invoke Magento PHP install script shell/install.php
977
     *
978
     * @param OutputInterface $output
979
     * @param string $installationFolder folder where magento is installed in, must exists setup script in
980
     * @param array $argv
981
     * @return void
982
     */
983
    private function runInstallScriptCommand(OutputInterface $output, $installationFolder, array $argv)
984
    {
985
        $installArgs = '';
986
        foreach ($argv as $argName => $argValue) {
987
            $installArgs .= '--' . $argName . ' ' . escapeshellarg($argValue) . ' ';
988
        }
989
990
        $output->writeln('<info>Start installation process.</info>');
991
992
        $installCommand = sprintf(
993
            '%s -ddisplay_startup_errors=1 -ddisplay_errors=1 -derror_reporting=-1 -f %s -- %s',
994
            OperatingSystem::getPhpBinary(),
995
            escapeshellarg($installationFolder . '/' . self::MAGENTO_INSTALL_SCRIPT_PATH),
996
            $installArgs
997
        );
998
999
        $output->writeln('<comment>' . $installCommand . '</comment>');
1000
        $installException = null;
1001
        $installationOutput = null;
1002
        $returnStatus = null;
1003
        try {
1004
            Exec::run($installCommand, $installationOutput, $returnStatus);
1005
        } catch (Exception $installException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1006
        }
1007
1008
        if (isset($installException) || $returnStatus !== Exec::CODE_CLEAN_EXIT) {
1009
            $this->getApplication()->setAutoExit(true);
1010
            throw new RuntimeException(
1011
                sprintf('Installation failed (Exit code %s). %s', $returnStatus, $installationOutput),
1012
                1,
1013
                $installException
1014
            );
1015
        }
1016
        $output->writeln('<info>Successfully installed Magento</info>');
1017
        $encryptionKey = trim(substr(strstr($installationOutput, ':'), 1));
1018
        $output->writeln('<comment>Encryption Key:</comment> <info>' . $encryptionKey . '</info>');
1019
    }
1020
}
1021