Completed
Push — develop ( 6f71dd...40edc3 )
by Tom
11s
created

InstallCommand::installSampleData()   D

Complexity

Conditions 13
Paths 107

Size

Total Lines 93
Code Lines 62

Duplication

Lines 6
Ratio 6.45 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 6
loc 93
rs 4.8671
cc 13
eloc 62
nc 107
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    const EXEC_STATUS_OK = 0;
35
    /**
36
     * @var array
37
     */
38
    protected $config;
39
40
    /**
41
     * @var array
42
     */
43
    protected $_argv;
44
45
    /**
46
     * @var array
47
     */
48
    protected $commandConfig;
49
50
    /**
51
     * @var \Closure
52
     */
53
    protected $notEmptyCallback;
54
55
    protected function configure()
56
    {
57
        $this
58
            ->setName('install')
59
            ->addOption('magentoVersion', null, InputOption::VALUE_OPTIONAL, 'Magento version')
60
            ->addOption(
61
                'magentoVersionByName',
62
                null,
63
                InputOption::VALUE_OPTIONAL,
64
                'Magento version name instead of order number'
65
            )
66
            ->addOption('installationFolder', null, InputOption::VALUE_OPTIONAL, 'Installation folder')
67
            ->addOption('dbHost', null, InputOption::VALUE_OPTIONAL, 'Database host')
68
            ->addOption('dbUser', null, InputOption::VALUE_OPTIONAL, 'Database user')
69
            ->addOption('dbPass', null, InputOption::VALUE_OPTIONAL, 'Database password')
70
            ->addOption('dbName', null, InputOption::VALUE_OPTIONAL, 'Database name')
71
            ->addOption('dbPort', null, InputOption::VALUE_OPTIONAL, 'Database port', 3306)
72
            ->addOption('dbPrefix', null, InputOption::VALUE_OPTIONAL, 'Table prefix', '')
73
            ->addOption('installSampleData', null, InputOption::VALUE_OPTIONAL, 'Install sample data')
74
            ->addOption(
75
                'useDefaultConfigParams',
76
                null,
77
                InputOption::VALUE_OPTIONAL,
78
                'Use default installation parameters defined in the yaml file'
79
            )->addOption('baseUrl', null, InputOption::VALUE_OPTIONAL, 'Installation base url')
80
            ->addOption(
81
                'replaceHtaccessFile',
82
                null,
83
                InputOption::VALUE_OPTIONAL,
84
                'Generate htaccess file (for non vhost environment)'
85
            )->addOption(
86
                'noDownload',
87
                null,
88
                InputOption::VALUE_NONE,
89
                'If set skips download step. Used when installationFolder is already a Magento installation that has ' .
90
                'to be installed on the given database.'
91
            )
92
            ->addOption(
93
                'only-download',
94
                null,
95
                InputOption::VALUE_NONE,
96
                'Downloads (and extracts) source code'
97
            )
98
            ->addOption(
99
                'forceUseDb',
100
                null,
101
                InputOption::VALUE_OPTIONAL,
102
                'If --noDownload passed, force to use given database if it already exists.'
103
            )->setDescription('Install magento');
104
105
        $help = <<<HELP
106
* Download Magento by a list of git repos and zip files (mageplus, 
107
  magelte, official community packages).
108
* Try to create database if it does not exist.
109
* Installs Magento sample data if available (since version 1.2.0).
110
* Starts Magento installer
111
* Sets rewrite base in .htaccess file
112
113
Example of an unattended Magento CE 1.7.0.2 installation:
114
115
   $ n98-magerun.phar install --dbHost="localhost" --dbUser="mydbuser" \
116
     --dbPass="mysecret" --dbName="magentodb" --installSampleData=yes \
117
     --useDefaultConfigParams=yes \
118
     --magentoVersionByName="magento-ce-1.7.0.2" \
119
     --installationFolder="magento" --baseUrl="http://magento.localdomain/"
120
121
Additionally, with --noDownload option you can install Magento working 
122
copy already stored in --installationFolder on the given database.
123
124
See it in action: http://youtu.be/WU-CbJ86eQc
125
126
HELP;
127
        $this->setHelp($help);
128
129
        $this->notEmptyCallback = function ($input) {
130
            if (empty($input)) {
131
                throw new InvalidArgumentException('Please enter a value');
132
            }
133
134
            return $input;
135
        };
136
    }
137
138
    /**
139
     * @return bool
140
     */
141
    public function isEnabled()
142
    {
143
        return Exec::allowed();
144
    }
145
146
    /**
147
     * @param InputInterface $input
148
     * @param OutputInterface $output
149
     * @throws RuntimeException
150
     * @return int|null|void
151
     */
152
    protected function execute(InputInterface $input, OutputInterface $output)
153
    {
154
        $this->commandConfig = $this->getCommandConfig();
155
        $this->writeSection($output, 'Magento Installation');
156
157
        $this->precheckPhp();
158
159
        if (!$input->getOption('noDownload')) {
160
            $this->selectMagentoVersion($input, $output);
161
        }
162
163
        $this->chooseInstallationFolder($input, $output);
164
165
        if (!$input->getOption('noDownload')) {
166
            $result = $this->downloadMagento($input, $output);
167
168
            if ($result === false) {
169
                return 1;
170
            }
171
        }
172
173
        if ($input->getOption('only-download')) {
174
            return 0;
175
        }
176
177
        $this->createDatabase($input, $output);
178
179
        if (!$input->getOption('noDownload')) {
180
            $this->installSampleData($input, $output);
181
        }
182
183
        $this->removeEmptyFolders();
184
        $this->setDirectoryPermissions($output);
185
        $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...
186
    }
187
188
    /**
189
     * Check PHP environment agains minimal required settings modules
190
     */
191
    protected function precheckPhp()
192
    {
193
        $extensions = $this->commandConfig['installation']['pre-check']['php']['extensions'];
194
        $missingExtensions = array();
195
        foreach ($extensions as $extension) {
196
            if (!extension_loaded($extension)) {
197
                $missingExtensions[] = $extension;
198
            }
199
        }
200
201
        if (count($missingExtensions) > 0) {
202
            throw new RuntimeException(
203
                'The following PHP extensions are required to start installation: ' . implode(',', $missingExtensions)
204
            );
205
        }
206
    }
207
208
    /**
209
     * @param InputInterface $input
210
     * @param OutputInterface $output
211
     *
212
     * @throws InvalidArgumentException
213
     */
214
    protected function selectMagentoVersion(InputInterface $input, OutputInterface $output)
215
    {
216
        if ($input->getOption('magentoVersion') == null && $input->getOption('magentoVersionByName') == null) {
217
            $question = array();
218
            foreach ($this->commandConfig['magento-packages'] as $key => $package) {
219
                $question[] = '<comment>' . str_pad('[' . ($key + 1) . ']', 4, ' ') . '</comment> ' .
220
                    $package['name'] . "\n";
221
            }
222
            $question[] = "<question>Choose a magento version:</question> ";
223
224
            $commandConfig = $this->commandConfig;
225
226
227
            $type = $this->getHelper('dialog')->askAndValidate(
228
                $output,
229
                $question,
230
                function ($typeInput) use ($commandConfig) {
231
                    if (!in_array($typeInput, range(1, count($commandConfig['magento-packages'])))) {
232
                        throw new InvalidArgumentException('Invalid type');
233
                    }
234
235
                    return $typeInput;
236
                }
237
            );
238
        } else {
239
            $type = null;
240
241
            if ($input->getOption('magentoVersion')) {
242
                $type = $input->getOption('magentoVersion');
243
                if ($type !== (string)(int)$type) {
244
                    $type = $this->getPackageNumberByName($type);
245
                }
246
            } elseif ($input->getOption('magentoVersionByName')) {
247
                $type = $this->getPackageNumberByName($input->getOption('magentoVersionByName'));
248
            }
249
250
            if ($type == null) {
251
                throw new InvalidArgumentException('Unable to locate Magento version');
252
            }
253
        }
254
255
        $magentoPackages = $this->commandConfig['magento-packages'];
256
257
        $index = $type - 1;
258
        if (!isset($magentoPackages[$index])) {
259
            throw new InvalidArgumentException(
260
                sprintf(
261
                    'Invalid Magento package number %s, must be from 1 to %d.',
262
                    var_export($type, true),
263
                    count($magentoPackages)
264
                )
265
            );
266
        }
267
268
        $this->config['magentoVersionData'] = $magentoPackages[$index];
269
    }
270
271
272
    /**
273
     * @param $name
274
     *
275
     * @return int 1 or greater as the one-based package number, null on failure to resolve the name
276
     */
277
    private function getPackageNumberByName($name)
278
    {
279
        // directly filter integer strings
280
        if ($name === (string)(int)$name) {
281
            return (int)$name;
282
        }
283
284
        $magentoPackages = $this->commandConfig['magento-packages'];
285
286
        foreach ($magentoPackages as $key => $package) {
287
            if ($package['name'] === $name) {
288
                return $key + 1;
289
            }
290
        }
291
292
        return null;
293
    }
294
295
    /**
296
     * @param InputInterface $input
297
     * @param OutputInterface $output
298
     * @return bool
299
     */
300
    public function downloadMagento(InputInterface $input, OutputInterface $output)
301
    {
302
        try {
303
            $package = $this->createComposerPackageByConfig($this->config['magentoVersionData']);
304
            $this->config['magentoPackage'] = $package;
305
306
            if (file_exists($this->config['installationFolder'] . '/app/Mage.php')) {
307
                $output->writeln('<error>A magento installation already exists in this folder </error>');
308
309
                return false;
310
            }
311
312
            $composer = $this->getComposer($input, $output);
313
            $targetFolder = $this->getTargetFolderByType($composer, $package, $this->config['installationFolder']);
314
            $this->config['magentoPackage'] = $this->downloadByComposerConfig(
315
                $input,
316
                $output,
317
                $package,
318
                $targetFolder,
319
                true
320
            );
321
322
            if ($this->isSourceTypeRepository($package->getSourceType())) {
323
                $filesystem = new \N98\Util\Filesystem;
324
                $filesystem->recursiveCopy($targetFolder, $this->config['installationFolder'], array('.git', '.hg'));
325
            } else {
326
                $filesystem = new \Composer\Util\Filesystem();
327
                $filesystem->copyThenRemove(
328
                    $this->config['installationFolder'] . '/_n98_magerun_download',
329
                    $this->config['installationFolder']
330
                );
331
            }
332
333
            if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
334
                // Patch installer
335
                $this->patchMagentoInstallerForPHP54($this->config['installationFolder']);
336
            }
337
        } catch (Exception $e) {
338
            $output->writeln('<error>' . $e->getMessage() . '</error>');
339
340
            return false;
341
        }
342
343
        return true;
344
    }
345
346
    /**
347
     * construct a folder to where magerun will download the source to, cache git/hg repositories under COMPOSER_HOME
348
     *
349
     * @param Composer $composer
350
     * @param CompletePackage $package
351
     * @param $installationFolder
352
     *
353
     * @return string
354
     */
355
    protected function getTargetFolderByType(Composer $composer, CompletePackage $package, $installationFolder)
356
    {
357
        $type = $package->getSourceType();
358
        if ($this->isSourceTypeRepository($type)) {
359
            $targetPath = sprintf(
360
                '%s/%s/%s/%s',
361
                $composer->getConfig()->get('cache-dir'),
362
                '_n98_magerun_download',
363
                $type,
364
                preg_replace('{[^a-z0-9.]}i', '-', $package->getSourceUrl())
365
            );
366
        } else {
367
            $targetPath = sprintf(
368
                '%s/%s',
369
                $installationFolder,
370
                '_n98_magerun_download'
371
            );
372
        }
373
374
        return $targetPath;
375
    }
376
377
    /**
378
     * @param string $magentoFolder
379
     */
380
    protected function patchMagentoInstallerForPHP54($magentoFolder)
381
    {
382
        $installerConfig = $magentoFolder
383
            . DIRECTORY_SEPARATOR
384
            . 'app/code/core/Mage/Install/etc/config.xml';
385
        if (file_exists($installerConfig)) {
386
            $xml = file_get_contents($installerConfig);
387
            file_put_contents($installerConfig, str_replace('<pdo_mysql/>', '<pdo_mysql>1</pdo_mysql>', $xml));
388
        }
389
    }
390
391
    /**
392
     * @param InputInterface $input
393
     * @param OutputInterface $output
394
     *
395
     * @throws InvalidArgumentException
396
     */
397
    protected function createDatabase(InputInterface $input, OutputInterface $output)
398
    {
399
        $dbOptions = array('--dbHost', '--dbUser', '--dbPass', '--dbName');
400
        $dbOptionsFound = 0;
401
        foreach ($dbOptions as $dbOption) {
402
            foreach ($this->getCliArguments() as $definedCliOption) {
403
                if (BinaryString::startsWith($definedCliOption, $dbOption)) {
404
                    $dbOptionsFound++;
405
                }
406
            }
407
        }
408
409
        $hasAllOptions = $dbOptionsFound == 4;
410
411
        // if all database options were passed in at cmd line
412
        if ($hasAllOptions) {
413
            $this->config['db_host'] = $input->getOption('dbHost');
414
            $this->config['db_user'] = $input->getOption('dbUser');
415
            $this->config['db_pass'] = $input->getOption('dbPass');
416
            $this->config['db_name'] = VerifyOrDie::filename(
417
                $input->getOption('dbName'),
418
                'Database name is not portable'
419
            );
420
            $this->config['db_port'] = $input->getOption('dbPort');
421
            $this->config['db_prefix'] = $input->getOption('dbPrefix');
422
            $db = $this->validateDatabaseSettings($output, $input);
423
424
            if ($db === false) {
425
                throw new InvalidArgumentException("Database configuration is invalid");
426
            }
427
        } else {
428
            /** @var DialogHelper $dialog */
429
            $dialog = $this->getHelperSet()->get('dialog');
430
            do {
431
                $dbHostDefault = $input->getOption('dbHost') ? $input->getOption('dbHost') : 'localhost';
432
                $this->config['db_host'] = $dialog->askAndValidate(
433
                    $output,
434
                    '<question>Please enter the database host</question> <comment>[' . $dbHostDefault . ']</comment>: ',
435
                    $this->notEmptyCallback,
436
                    false,
437
                    $dbHostDefault
438
                );
439
440
                $dbUserDefault = $input->getOption('dbUser') ? $input->getOption('dbUser') : 'root';
441
                $this->config['db_user'] = $dialog->askAndValidate(
442
                    $output,
443
                    '<question>Please enter the database username</question> <comment>[' . $dbUserDefault .
444
                    ']</comment>: ',
445
                    $this->notEmptyCallback,
446
                    false,
447
                    $dbUserDefault
448
                );
449
450
                $dbPassDefault = $input->getOption('dbPass') ? $input->getOption('dbPass') : '';
451
                $this->config['db_pass'] = $dialog->ask(
452
                    $output,
453
                    '<question>Please enter the database password</question> <comment>[' . $dbPassDefault .
454
                    ']</comment>: ',
455
                    $dbPassDefault
456
                );
457
458
                $dbNameDefault = $input->getOption('dbName') ? $input->getOption('dbName') : 'magento';
459
                $this->config['db_name'] = $dialog->askAndValidate(
460
                    $output,
461
                    '<question>Please enter the database name</question> <comment>[' . $dbNameDefault . ']</comment>: ',
462
                    $this->notEmptyCallback,
463
                    false,
464
                    $dbNameDefault
465
                );
466
467
                $dbPortDefault = $input->getOption('dbPort') ? $input->getOption('dbPort') : 3306;
468
                $this->config['db_port'] = $dialog->askAndValidate(
469
                    $output,
470
                    '<question>Please enter the database port </question> <comment>[' . $dbPortDefault .
471
                    ']</comment>: ',
472
                    $this->notEmptyCallback,
473
                    false,
474
                    $dbPortDefault
475
                );
476
477
                $dbPrefixDefault = $input->getOption('dbPrefix') ? $input->getOption('dbPrefix') : '';
478
                $this->config['db_prefix'] = $dialog->ask(
479
                    $output,
480
                    '<question>Please enter the table prefix</question> <comment>[' . $dbPrefixDefault . ']</comment>:',
481
                    $dbPrefixDefault
482
                );
483
                $db = $this->validateDatabaseSettings($output, $input);
484
            } while ($db === false);
485
        }
486
487
        $this->config['db'] = $db;
488
    }
489
490
    /**
491
     * @param OutputInterface $output
492
     * @param InputInterface $input
493
     *
494
     * @return bool|PDO
495
     */
496
    protected function validateDatabaseSettings(OutputInterface $output, InputInterface $input)
497
    {
498
        try {
499
            $dsn = sprintf("mysql:host=%s;port=%s", $this->config['db_host'], $this->config['db_port']);
500
            $db = new PDO($dsn, $this->config['db_user'], $this->config['db_pass']);
501
            if (!$db->query('USE ' . $this->config['db_name'])) {
502
                $db->query("CREATE DATABASE `" . $this->config['db_name'] . "`");
503
                $output->writeln('<info>Created database ' . $this->config['db_name'] . '</info>');
504
                $db->query('USE ' . $this->config['db_name']);
505
506
                return $db;
507
            }
508
509
            if ($input->getOption('noDownload') && !$input->getOption('forceUseDb')) {
510
                $output->writeln("<error>Database {$this->config['db_name']} already exists.</error>");
511
512
                return false;
513
            }
514
515
            return $db;
516
        } catch (PDOException $e) {
517
            $output->writeln('<error>' . $e->getMessage() . '</error>');
518
        }
519
520
        return false;
521
    }
522
523
    /**
524
     * @param InputInterface $input
525
     * @param OutputInterface $output
526
     */
527
    protected function installSampleData(InputInterface $input, OutputInterface $output)
528
    {
529
        $magentoPackage = $this->config['magentoPackage'];
530
        /* @var $magentoPackage \Composer\Package\MemoryPackage */
531
        $extra = $magentoPackage->getExtra();
532
        if (!isset($extra['sample-data'])) {
533
            return;
534
        }
535
536
        $dialog = $this->getHelperSet()->get('dialog');
537
538
        $installSampleData = ($input->getOption('installSampleData') !== null)
539
            ? $this->_parseBoolOption($input->getOption('installSampleData'))
540
            : $dialog->askConfirmation(
541
                $output,
542
                '<question>Install sample data?</question> <comment>[y]</comment>: '
543
            );
544
545
        if ($installSampleData) {
546
            $filesystem = new Filesystem();
547
548
            foreach ($this->commandConfig['demo-data-packages'] as $demoPackageData) {
549
                if ($demoPackageData['name'] == $extra['sample-data']) {
550
                    $package = $this->downloadByComposerConfig(
551
                        $input,
552
                        $output,
553
                        $demoPackageData,
554
                        $this->config['installationFolder'] . '/_temp_demo_data',
555
                        false
556
                    );
557
558
                    $this->_fixComposerExtractionBug();
559
560
                    $expandedFolder = $this->config['installationFolder']
561
                        . '/_temp_demo_data/'
562
                        . str_replace(array('.tar.gz', '.tar.bz2', '.zip'), '', basename($package->getDistUrl()));
563
                    if (is_dir($expandedFolder)) {
564
                        $filesystem->recursiveCopy(
565
                            $expandedFolder,
566
                            $this->config['installationFolder']
567
                        );
568
                        $filesystem->recursiveRemoveDirectory($expandedFolder);
569
                    }
570
571
                    // Remove empty folder
572 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...
573
                        $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/vendor/composer');
574
                    }
575
576
                    // Install sample data
577
                    $sampleDataSqlFile = glob(
578
                        $this->config['installationFolder'] . '/_temp_demo_data/magento_*sample_data*sql'
579
                    );
580
                    $db = $this->config['db'];
581
                    /* @var $db PDO */
582
                    if (isset($sampleDataSqlFile[0])) {
583
                        if (OperatingSystem::isProgramInstalled('mysql')) {
584
                            $exec = 'mysql '
585
                                . '-h' . escapeshellarg(strval($this->config['db_host']))
586
                                . ' '
587
                                . '-u' . escapeshellarg(strval($this->config['db_user']))
588
                                . ' '
589
                                . ($this->config['db_port'] != '3306'
590
                                    ? '-P' . escapeshellarg($this->config['db_port']) . ' ' : '')
591
                                . (!strval($this->config['db_pass'] == '')
592
                                    ? '-p' . escapeshellarg($this->config['db_pass']) . ' ' : '')
593
                                . strval($this->config['db_name'])
594
                                . ' < '
595
                                . escapeshellarg($sampleDataSqlFile[0]);
596
                            $output->writeln(
597
                                '<info>Importing <comment>' . $sampleDataSqlFile[0] .
598
                                '</comment> with mysql cli client</info>'
599
                            );
600
                            Exec::run($exec);
601
                            @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...
602
                        } else {
603
                            $output->writeln(
604
                                '<info>Importing <comment>' . $sampleDataSqlFile[0] .
605
                                '</comment> with PDO driver</info>'
606
                            );
607
                            // Fallback -> Try to install dump file by PDO driver
608
                            $dbUtils = new DatabaseUtils();
609
                            $dbUtils->importSqlDump($db, $sampleDataSqlFile[0]);
610
                        }
611
                    }
612
                }
613
            }
614
615 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...
616
                $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/_temp_demo_data');
617
            }
618
        }
619
    }
620
621
    protected function _fixComposerExtractionBug()
622
    {
623
        $filesystem = new Filesystem();
624
        foreach (array('/_temp_demo_data/media' => '/media', '/_temp_demo_data/skin' => '/skin') as $wrong => $right) {
625
            $wrongFolder = $this->config['installationFolder'] . $wrong;
626
            $rightFolder = $this->config['installationFolder'] . $right;
627
            if (is_dir($wrongFolder)) {
628
                $filesystem->recursiveCopy(
629
                    $wrongFolder,
630
                    $rightFolder
631
                );
632
                $filesystem->recursiveRemoveDirectory($wrongFolder);
633
            }
634
        }
635
    }
636
637
    /**
638
     * Remove empty composer extraction folder
639
     */
640
    protected function removeEmptyFolders()
641
    {
642
        if (is_dir(getcwd() . '/vendor')) {
643
            $finder = new Finder();
644
            $finder->files()->depth(3)->in(getcwd() . '/vendor');
645
            if ($finder->count() == 0) {
646
                $filesystem = new Filesystem();
647
                $filesystem->recursiveRemoveDirectory(getcwd() . '/vendor');
648
            }
649
        }
650
    }
651
652
    /**
653
     * @param InputInterface $input
654
     * @param OutputInterface $output
655
     *
656
     * @return array
657
     * @throws InvalidArgumentException parameter mismatch (e.g. base-url components like hostname)
658
     * @throws RuntimeException
659
     */
660
    protected function installMagento(InputInterface $input, OutputInterface $output)
661
    {
662
        $this->getApplication()->setAutoExit(false);
663
        $dialog = $this->getHelperSet()->get('dialog');
664
665
        $defaults = $this->commandConfig['installation']['defaults'];
666
667
        $useDefaultConfigParams = $this->_parseBoolOption($input->getOption('useDefaultConfigParams'));
668
669
        $sessionSave = $useDefaultConfigParams ? $defaults['session_save'] : $dialog->ask(
670
            $output,
671
            '<question>Please enter the session save:</question> <comment>[' .
672
            $defaults['session_save'] . ']</comment>: ',
673
            $defaults['session_save']
674
        );
675
676
        $adminFrontname = $useDefaultConfigParams ? $defaults['admin_frontname'] : $dialog->askAndValidate(
677
            $output,
678
            '<question>Please enter the admin frontname:</question> <comment>[' .
679
            $defaults['admin_frontname'] . ']</comment> ',
680
            $this->notEmptyCallback,
681
            false,
682
            $defaults['admin_frontname']
683
        );
684
685
        $currency = $useDefaultConfigParams ? $defaults['currency'] : $dialog->askAndValidate(
686
            $output,
687
            '<question>Please enter the default currency code:</question> <comment>[' .
688
            $defaults['currency'] . ']</comment>: ',
689
            $this->notEmptyCallback,
690
            false,
691
            $defaults['currency']
692
        );
693
694
        $locale = $useDefaultConfigParams ? $defaults['locale'] : $dialog->askAndValidate(
695
            $output,
696
            '<question>Please enter the locale code:</question> <comment>[' . $defaults['locale'] . ']</comment>: ',
697
            $this->notEmptyCallback,
698
            false,
699
            $defaults['locale']
700
        );
701
702
        $timezone = $useDefaultConfigParams ? $defaults['timezone'] : $dialog->askAndValidate(
703
            $output,
704
            '<question>Please enter the timezone:</question> <comment>[' . $defaults['timezone'] . ']</comment>: ',
705
            $this->notEmptyCallback,
706
            false,
707
            $defaults['timezone']
708
        );
709
710
        $adminUsername = $useDefaultConfigParams ? $defaults['admin_username'] : $dialog->askAndValidate(
711
            $output,
712
            '<question>Please enter the admin username:</question> <comment>[' .
713
            $defaults['admin_username'] . ']</comment>: ',
714
            $this->notEmptyCallback,
715
            false,
716
            $defaults['admin_username']
717
        );
718
719
        $adminPassword = $useDefaultConfigParams ? $defaults['admin_password'] : $dialog->askAndValidate(
720
            $output,
721
            '<question>Please enter the admin password:</question> <comment>[' .
722
            $defaults['admin_password'] . ']</comment>: ',
723
            $this->notEmptyCallback,
724
            false,
725
            $defaults['admin_password']
726
        );
727
728
        $adminFirstname = $useDefaultConfigParams ? $defaults['admin_firstname'] : $dialog->askAndValidate(
729
            $output,
730
            '<question>Please enter the admin\'s firstname:</question> <comment>[' .
731
            $defaults['admin_firstname'] . ']</comment>: ',
732
            $this->notEmptyCallback,
733
            false,
734
            $defaults['admin_firstname']
735
        );
736
737
        $adminLastname = $useDefaultConfigParams ? $defaults['admin_lastname'] : $dialog->askAndValidate(
738
            $output,
739
            '<question>Please enter the admin\'s lastname:</question> <comment>[' .
740
            $defaults['admin_lastname'] . ']</comment>: ',
741
            $this->notEmptyCallback,
742
            false,
743
            $defaults['admin_lastname']
744
        );
745
746
        $adminEmail = $useDefaultConfigParams ? $defaults['admin_email'] : $dialog->askAndValidate(
747
            $output,
748
            '<question>Please enter the admin\'s email:</question> <comment>[' .
749
            $defaults['admin_email'] . ']</comment>: ',
750
            $this->notEmptyCallback,
751
            false,
752
            $defaults['admin_email']
753
        );
754
755
        $validateBaseUrl = function ($input) {
756
            if (!preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $input)) {
757
                throw new InvalidArgumentException('Please enter a valid URL');
758
            }
759
            if (parse_url($input, \PHP_URL_HOST) == 'localhost') {
760
                throw new InvalidArgumentException(
761
                    'localhost cause problems! Please use 127.0.0.1 or another hostname'
762
                );
763
            }
764
765
            return $input;
766
        };
767
768
        $baseUrl = ($input->getOption('baseUrl') !== null) ? $input->getOption('baseUrl') : $dialog->askAndValidate(
769
            $output,
770
            '<question>Please enter the base url:</question> ',
771
            $validateBaseUrl,
772
            false
773
        );
774
        $baseUrl = rtrim($baseUrl, '/') . '/'; // normalize baseUrl
775
776
        /**
777
         * Correct session save (common mistake)
778
         */
779
        if ($sessionSave == 'file') {
780
            $sessionSave = 'files';
781
        }
782
783
        /**
784
         * Try to create session folder
785
         */
786
        $defaultSessionFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/session';
787
        if ($sessionSave == 'files' && !is_dir($defaultSessionFolder)) {
788
            @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...
789
        }
790
791
        $dbHost = $this->config['db_host'];
792
        if ($this->config['db_port'] != 3306) {
793
            $dbHost .= ':' . $this->config['db_port'];
794
        }
795
796
        $argv = array(
797
            'license_agreement_accepted' => 'yes',
798
            'locale'                     => $locale,
799
            'timezone'                   => $timezone,
800
            'db_host'                    => $dbHost,
801
            'db_name'                    => $this->config['db_name'],
802
            'db_user'                    => $this->config['db_user'],
803
            'db_pass'                    => $this->config['db_pass'],
804
            'db_prefix'                  => $this->config['db_prefix'],
805
            'url'                        => $baseUrl,
806
            'use_rewrites'               => 'yes',
807
            'use_secure'                 => 'no',
808
            'secure_base_url'            => '',
809
            'use_secure_admin'           => 'no',
810
            'admin_username'             => $adminUsername,
811
            'admin_lastname'             => $adminLastname,
812
            'admin_firstname'            => $adminFirstname,
813
            'admin_email'                => $adminEmail,
814
            'admin_password'             => $adminPassword,
815
            'session_save'               => $sessionSave,
816
            'admin_frontname'            => $adminFrontname, /* magento 1 */
817
            'backend_frontname'          => $adminFrontname, /* magento 2 */
818
            'default_currency'           => $currency,
819
            'skip_url_validation'        => 'yes',
820
        );
821
        if ($useDefaultConfigParams) {
822
            if (strlen($defaults['encryption_key']) > 0) {
823
                $argv['encryption_key'] = $defaults['encryption_key'];
824
            }
825
            if (strlen($defaults['use_secure']) > 0) {
826
                $argv['use_secure'] = $defaults['use_secure'];
827
                $argv['secure_base_url'] = str_replace('http://', 'https://', $baseUrl);
828
            }
829
            if (strlen($defaults['use_rewrites']) > 0) {
830
                $argv['use_rewrites'] = $defaults['use_rewrites'];
831
            }
832
        }
833
        $installArgs = '';
834
        foreach ($argv as $argName => $argValue) {
835
            $installArgs .= '--' . $argName . ' ' . escapeshellarg($argValue) . ' ';
836
        }
837
838
        $output->writeln('<info>Start installation process.</info>');
839
840
        $phpExec = OperatingSystem::getPhpBinary();
841
        $installCommand = $phpExec . ' -f ' . escapeshellarg($this->getInstallScriptPath()) . ' -- ' . $installArgs;
842
843
844
        $output->writeln('<comment>' . $installCommand . '</comment>');
845
        Exec::run($installCommand, $installationOutput, $returnStatus);
846
        if ($returnStatus !== self::EXEC_STATUS_OK) {
847
            $this->getApplication()->setAutoExit(true);
848
            throw new RuntimeException('Installation failed.' . $installationOutput, 1);
849
        } else {
850
            $output->writeln('<info>Successfully installed Magento</info>');
851
            $encryptionKey = trim(substr($installationOutput, strpos($installationOutput, ':') + 1));
852
            $output->writeln('<comment>Encryption Key:</comment> <info>' . $encryptionKey . '</info>');
853
        }
854
855
        $dialog = $this->getHelperSet()->get('dialog');
856
857
        /**
858
         * Htaccess file
859
         */
860
        if ($input->getOption('useDefaultConfigParams') == null || $input->getOption('replaceHtaccessFile') != null) {
861
            $replaceHtaccessFile = false;
862
863
            if ($this->_parseBoolOption($input->getOption('replaceHtaccessFile'))) {
864
                $replaceHtaccessFile = true;
865
            } elseif ($dialog->askConfirmation(
866
                $output,
867
                '<question>Write BaseURL to .htaccess file?</question> <comment>[n]</comment>: ',
868
                false
869
            )
870
            ) {
871
                $replaceHtaccessFile = true;
872
            }
873
874
            if ($replaceHtaccessFile) {
875
                $this->replaceHtaccessFile($baseUrl);
876
            }
877
        }
878
879
        \chdir($this->config['installationFolder']);
880
        $this->getApplication()->reinit();
881
        $output->writeln('<info>Reindex all after installation</info>');
882
        $this->getApplication()->run(new StringInput('index:reindex:all'), $output);
883
        $this->getApplication()->run(new StringInput('sys:check'), $output);
884
        $output->writeln('<info>Successfully installed magento</info>');
885
    }
886
887
    /**
888
     * Check if we have a magento 2 or 1 installation and return path to install.php
889
     *
890
     * @return string
891
     */
892
    protected function getInstallScriptPath()
893
    {
894
        $magento1InstallScriptPath = $this->config['installationFolder'] . '/install.php';
895
        $magento2InstallScriptPath = $this->config['installationFolder'] . '/dev/shell/install.php';
896
        if (file_exists($magento2InstallScriptPath)) {
897
            return $magento2InstallScriptPath;
898
        }
899
900
        return $magento1InstallScriptPath;
901
    }
902
903
    /**
904
     * @param string $baseUrl
905
     */
906
    protected function replaceHtaccessFile($baseUrl)
907
    {
908
        $content = file_get_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess');
909
        copy(
910
            $this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess',
911
            $this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess.dist'
912
        );
913
        $content = str_replace('#RewriteBase /magento/', 'RewriteBase ' . parse_url($baseUrl, PHP_URL_PATH), $content);
914
        file_put_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess', $content);
915
    }
916
917
    /**
918
     * @param OutputInterface $output
919
     */
920
    protected function setDirectoryPermissions($output)
921
    {
922
        try {
923
            $varFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var';
924
            if (!is_dir($varFolder)) {
925
                @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...
926
            }
927
            @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...
928
929
            $varCacheFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/cache';
930
            if (!is_dir($varCacheFolder)) {
931
                @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...
932
            }
933
            @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...
934
935
            $mediaFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'media';
936
            if (!is_dir($mediaFolder)) {
937
                @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...
938
            }
939
            @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...
940
941
            $finder = Finder::create();
942
            $finder->directories()
943
                ->ignoreUnreadableDirs(true)
944
                ->in(array($varFolder, $mediaFolder));
945
            foreach ($finder as $dir) {
946
                @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...
947
            }
948
        } catch (Exception $e) {
949
            $output->writeln('<error>' . $e->getMessage() . '</error>');
950
        }
951
    }
952
953
    /**
954
     * @return array
955
     */
956
    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...
957
    {
958
        if ($this->_argv === null) {
959
            $this->_argv = $_SERVER['argv'];
960
        }
961
962
        return $this->_argv;
963
    }
964
965
    /**
966
     * @param array $args
967
     */
968
    public function setCliArguments($args)
969
    {
970
        $this->_argv = $args;
971
    }
972
}
973