Completed
Push — develop ( 62543a...9b88b3 )
by Tom
04:35
created

InstallCommand::isEnabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
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
    const DEFAULT_SESSION_PATH = 'var/session';
40
    const MAGENTO_INSTALL_SCRIPT_PATH = 'dev/shell/install.php';
41
42
    /**
43
     * @var array
44
     */
45
    protected $config;
46
47
    /**
48
     * @var array
49
     */
50
    protected $_argv;
51
52
    /**
53
     * @var array
54
     */
55
    protected $commandConfig;
56
57
    /**
58
     * @var \Closure
59
     */
60
    protected $notEmptyCallback;
61
62
    protected function configure()
63
    {
64
        $this
65
            ->setName('install')
66
            ->addOption('magentoVersion', null, InputOption::VALUE_OPTIONAL, 'Magento version')
67
            ->addOption(
68
                'magentoVersionByName',
69
                null,
70
                InputOption::VALUE_OPTIONAL,
71
                'Magento version name instead of order number'
72
            )
73
            ->addOption('installationFolder', null, InputOption::VALUE_OPTIONAL, 'Installation folder')
74
            ->addOption('dbHost', null, InputOption::VALUE_OPTIONAL, 'Database host')
75
            ->addOption('dbUser', null, InputOption::VALUE_OPTIONAL, 'Database user')
76
            ->addOption('dbPass', null, InputOption::VALUE_OPTIONAL, 'Database password')
77
            ->addOption('dbName', null, InputOption::VALUE_OPTIONAL, 'Database name')
78
            ->addOption('dbPort', null, InputOption::VALUE_OPTIONAL, 'Database port', 3306)
79
            ->addOption('dbPrefix', null, InputOption::VALUE_OPTIONAL, 'Table prefix', '')
80
            ->addOption('installSampleData', null, InputOption::VALUE_OPTIONAL, 'Install sample data')
81
            ->addOption(
82
                'useDefaultConfigParams',
83
                null,
84
                InputOption::VALUE_OPTIONAL,
85
                'Use default installation parameters defined in the yaml file'
86
            )->addOption('baseUrl', null, InputOption::VALUE_OPTIONAL, 'Installation base url')
87
            ->addOption(
88
                'replaceHtaccessFile',
89
                null,
90
                InputOption::VALUE_OPTIONAL,
91
                'Generate htaccess file (for non vhost environment)'
92
            )->addOption(
93
                'noDownload',
94
                null,
95
                InputOption::VALUE_NONE,
96
                'If set skips download step. Used when installationFolder is already a Magento installation that has ' .
97
                'to be installed on the given database.'
98
            )
99
            ->addOption(
100
                'only-download',
101
                null,
102
                InputOption::VALUE_NONE,
103
                'Downloads (and extracts) source code'
104
            )
105
            ->addOption(
106
                'forceUseDb',
107
                null,
108
                InputOption::VALUE_NONE,
109
                'If --noDownload passed, force to use given database if it already exists.'
110
            )->setDescription('Install magento');
111
112
        $help = <<<HELP
113
* Download Magento by a list of git repos and zip files (mageplus, 
114
  magelte, official community packages).
115
* Try to create database if it does not exist.
116
* Installs Magento sample data if available (since version 1.2.0).
117
* Starts Magento installer
118
* Sets rewrite base in .htaccess file
119
120
Example of an unattended Magento CE 1.7.0.2 installation:
121
122
   $ n98-magerun.phar install --dbHost="localhost" --dbUser="mydbuser" \
123
     --dbPass="mysecret" --dbName="magentodb" --installSampleData=yes \
124
     --useDefaultConfigParams=yes \
125
     --magentoVersionByName="magento-ce-1.7.0.2" \
126
     --installationFolder="magento" --baseUrl="http://magento.localdomain/"
127
128
Additionally, with --noDownload option you can install Magento working 
129
copy already stored in --installationFolder on the given database.
130
131
See it in action: http://youtu.be/WU-CbJ86eQc
132
133
HELP;
134
        $this->setHelp($help);
135
136
        $this->notEmptyCallback = function ($input) {
137
            if (empty($input)) {
138
                throw new InvalidArgumentException('Please enter a value');
139
            }
140
141
            return $input;
142
        };
143
    }
144
145
    /**
146
     * @return bool
147
     */
148
    public function isEnabled()
149
    {
150
        return Exec::allowed();
151
    }
152
153
    /**
154
     * @param InputInterface $input
155
     * @param OutputInterface $output
156
     * @throws RuntimeException
157
     * @return int|null|void
158
     */
159
    protected function execute(InputInterface $input, OutputInterface $output)
160
    {
161
        $this->commandConfig = $this->getCommandConfig();
162
        $this->writeSection($output, 'Magento Installation');
163
164
        $this->precheckPhp();
165
166
        if (!$input->getOption('noDownload')) {
167
            $this->selectMagentoVersion($input, $output);
168
        }
169
170
        $this->chooseInstallationFolder($input, $output);
171
172
        if (!$input->getOption('noDownload')) {
173
            $result = $this->downloadMagento($input, $output);
174
175
            if ($result === false) {
176
                return 1;
177
            }
178
        }
179
180
        if ($input->getOption('only-download')) {
181
            return 0;
182
        }
183
184
        $this->createDatabase($input, $output);
185
186
        if (!$input->getOption('noDownload')) {
187
            $this->installSampleData($input, $output);
188
        }
189
190
        $this->removeEmptyFolders();
191
        $this->setDirectoryPermissions($output);
192
        $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...
193
    }
194
195
    /**
196
     * Check PHP environment agains minimal required settings modules
197
     */
198
    protected function precheckPhp()
199
    {
200
        $extensions = $this->commandConfig['installation']['pre-check']['php']['extensions'];
201
        $missingExtensions = array();
202
        foreach ($extensions as $extension) {
203
            if (!extension_loaded($extension)) {
204
                $missingExtensions[] = $extension;
205
            }
206
        }
207
208
        if (count($missingExtensions) > 0) {
209
            throw new RuntimeException(
210
                'The following PHP extensions are required to start installation: ' . implode(',', $missingExtensions)
211
            );
212
        }
213
    }
214
215
    /**
216
     * @param InputInterface $input
217
     * @param OutputInterface $output
218
     *
219
     * @throws InvalidArgumentException
220
     */
221
    protected function selectMagentoVersion(InputInterface $input, OutputInterface $output)
222
    {
223
        if ($input->getOption('magentoVersion') == null && $input->getOption('magentoVersionByName') == null) {
224
            $question = array();
225
            foreach ($this->commandConfig['magento-packages'] as $key => $package) {
226
                $question[] = '<comment>' . str_pad('[' . ($key + 1) . ']', 4, ' ') . '</comment> ' .
227
                    $package['name'] . "\n";
228
            }
229
            $question[] = "<question>Choose a magento version:</question> ";
230
231
            $commandConfig = $this->commandConfig;
232
233
234
            $type = $this->getHelper('dialog')->askAndValidate(
235
                $output,
236
                $question,
237
                function ($typeInput) use ($commandConfig) {
238
                    if (!in_array($typeInput, range(1, count($commandConfig['magento-packages'])))) {
239
                        throw new InvalidArgumentException('Invalid type');
240
                    }
241
242
                    return $typeInput;
243
                }
244
            );
245
        } else {
246
            $type = null;
247
248
            if ($input->getOption('magentoVersion')) {
249
                $type = $input->getOption('magentoVersion');
250
                if ($type !== (string)(int)$type) {
251
                    $type = $this->getPackageNumberByName($type);
252
                }
253
            } elseif ($input->getOption('magentoVersionByName')) {
254
                $type = $this->getPackageNumberByName($input->getOption('magentoVersionByName'));
255
            }
256
257
            if ($type == null) {
258
                throw new InvalidArgumentException('Unable to locate Magento version');
259
            }
260
        }
261
262
        $magentoPackages = $this->commandConfig['magento-packages'];
263
264
        $index = $type - 1;
265
        if (!isset($magentoPackages[$index])) {
266
            throw new InvalidArgumentException(
267
                sprintf(
268
                    'Invalid Magento package number %s, must be from 1 to %d.',
269
                    var_export($type, true),
270
                    count($magentoPackages)
271
                )
272
            );
273
        }
274
275
        $this->config['magentoVersionData'] = $magentoPackages[$index];
276
    }
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 null;
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->getHelperSet()->get('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->getHelperSet()->get('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->getHelperSet()->get('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
        $dialog = $this->getHelperSet()->get('dialog');
857
858
        /**
859
         * Htaccess file
860
         */
861
        if ($input->getOption('useDefaultConfigParams') == null || $input->getOption('replaceHtaccessFile') != null) {
862
            $replaceHtaccessFile = false;
863
864
            if ($this->_parseBoolOption($input->getOption('replaceHtaccessFile'))) {
865
                $replaceHtaccessFile = true;
866
            } elseif ($dialog->askConfirmation(
867
                $output,
868
                '<question>Write BaseURL to .htaccess file?</question> <comment>[n]</comment>: ',
869
                false
870
            )
871
            ) {
872
                $replaceHtaccessFile = true;
873
            }
874
875
            if ($replaceHtaccessFile) {
876
                $this->replaceHtaccessFile($baseUrl);
877
            }
878
        }
879
880
        \chdir($this->config['installationFolder']);
881
        $this->getApplication()->reinit();
882
        $output->writeln('<info>Reindex all after installation</info>');
883
        $this->getApplication()->run(new StringInput('index:reindex:all'), $output);
884
        $this->getApplication()->run(new StringInput('sys:check'), $output);
885
        $output->writeln('<info>Successfully installed magento</info>');
886
    }
887
888
    /**
889
     * Check if we have a magento 2 or 1 installation and return path to install.php
890
     *
891
     * @deprecated since 1.97.22; no Magento 1 / 2 dynamic needed in the API, use MAGENTO_INSTALL_SCRIPT_PATH
892
     *             constant instead.
893
     * @return string
894
     */
895
    protected function getInstallScriptPath()
896
    {
897
        trigger_error(__METHOD__ . ' will be removed from API', E_USER_DEPRECATED);
898
        return $this->config['installationFolder'] . '/' . self::MAGENTO_INSTALL_SCRIPT_PATH;
899
    }
900
901
    /**
902
     * @param string $baseUrl
903
     */
904
    protected function replaceHtaccessFile($baseUrl)
905
    {
906
        $content = file_get_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess');
907
        copy(
908
            $this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess',
909
            $this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess.dist'
910
        );
911
        $content = str_replace('#RewriteBase /magento/', 'RewriteBase ' . parse_url($baseUrl, PHP_URL_PATH), $content);
912
        file_put_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess', $content);
913
    }
914
915
    /**
916
     * @param OutputInterface $output
917
     */
918
    protected function setDirectoryPermissions($output)
919
    {
920
        try {
921
            $varFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var';
922
            if (!is_dir($varFolder)) {
923
                @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...
924
            }
925
            @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...
926
927
            $varCacheFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/cache';
928
            if (!is_dir($varCacheFolder)) {
929
                @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...
930
            }
931
            @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...
932
933
            $mediaFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'media';
934
            if (!is_dir($mediaFolder)) {
935
                @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...
936
            }
937
            @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...
938
939
            $finder = Finder::create();
940
            $finder->directories()
941
                ->ignoreUnreadableDirs(true)
942
                ->in(array($varFolder, $mediaFolder));
943
            foreach ($finder as $dir) {
944
                @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...
945
            }
946
        } catch (Exception $e) {
947
            $output->writeln('<error>' . $e->getMessage() . '</error>');
948
        }
949
    }
950
951
    /**
952
     * @return array
953
     */
954
    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...
955
    {
956
        if ($this->_argv === null) {
957
            $this->_argv = $_SERVER['argv'];
958
        }
959
960
        return $this->_argv;
961
    }
962
963
    /**
964
     * @param array $args
965
     */
966
    public function setCliArguments($args)
967
    {
968
        $this->_argv = $args;
969
    }
970
971
    /**
972
     * Invoke Magento PHP install script shell/install.php
973
     *
974
     * @param OutputInterface $output
975
     * @param string $installationFolder folder where magento is installed in, must exists setup script in
976
     * @param array $argv
977
     * @return void
978
     */
979
    private function runInstallScriptCommand(OutputInterface $output, $installationFolder, array $argv)
980
    {
981
        $installArgs = '';
982
        foreach ($argv as $argName => $argValue) {
983
            $installArgs .= '--' . $argName . ' ' . escapeshellarg($argValue) . ' ';
984
        }
985
986
        $output->writeln('<info>Start installation process.</info>');
987
988
        $installCommand = sprintf(
989
            '%s -f %s -- %s',
990
            OperatingSystem::getPhpBinary(),
991
            escapeshellarg($installationFolder . '/' . self::MAGENTO_INSTALL_SCRIPT_PATH),
992
            $installArgs
993
        );
994
995
        $output->writeln('<comment>' . $installCommand . '</comment>');
996
        $installException = null;
997
        $installationOutput = null;
998
        $returnStatus = null;
999
        try {
1000
            Exec::run($installCommand, $installationOutput, $returnStatus);
1001
        } catch (Exception $installException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1002
        }
1003
1004
        if (isset($installException) || $returnStatus !== Exec::CODE_CLEAN_EXIT) {
1005
            $this->getApplication()->setAutoExit(true);
1006
            throw new RuntimeException(
1007
                sprintf('Installation failed (Exit code %s). %s', $returnStatus, $installationOutput),
1008
                1,
1009
                $installException
1010
            );
1011
        }
1012
        $output->writeln('<info>Successfully installed Magento</info>');
1013
        $encryptionKey = trim(substr(strstr($installationOutput, ':'), 1));
1014
        $output->writeln('<comment>Encryption Key:</comment> <info>' . $encryptionKey . '</info>');
1015
    }
1016
}
1017