Completed
Pull Request — master (#744)
by
unknown
07:45 queued 17s
created

InstallCommand::getPhpExecutable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 12
rs 9.4286
cc 3
eloc 8
nc 3
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\Database as DatabaseUtils;
11
use N98\Util\Filesystem;
12
use N98\Util\OperatingSystem;
13
use N98\Util\BinaryString;
14
use PDO;
15
use PDOException;
16
use RuntimeException;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Input\InputOption;
19
use Symfony\Component\Console\Input\StringInput;
20
use Symfony\Component\Console\Output\OutputInterface;
21
use Symfony\Component\Finder\Finder;
22
use N98\Util\Exec;
23
24
/**
25
 * Class InstallCommand
26
 *
27
 * @codeCoverageIgnore  - Travis server uses installer to create a new shop. If it not works complete build fails.
28
 * @package N98\Magento\Command\Installer
29
 */
30
class InstallCommand extends AbstractMagentoCommand
31
{
32
    const EXEC_STATUS_OK = 0;
33
    /**
34
     * @var array
35
     */
36
    protected $config;
37
38
    /**
39
     * @var array
40
     */
41
    protected $_argv;
42
43
    /**
44
     * @var array
45
     */
46
    protected $commandConfig;
47
48
    /**
49
     * @var \Closure
50
     */
51
    protected $notEmptyCallback;
52
53
    protected function configure()
54
    {
55
        $this
56
            ->setName('install')
57
            ->addOption('magentoVersion', null, InputOption::VALUE_OPTIONAL, 'Magento version')
58
            ->addOption('magentoVersionByName', null, InputOption::VALUE_OPTIONAL, 'Magento version name instead of order number')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
59
            ->addOption('installationFolder', null, InputOption::VALUE_OPTIONAL, 'Installation folder')
60
            ->addOption('dbHost', null, InputOption::VALUE_OPTIONAL, 'Database host')
61
            ->addOption('dbUser', null, InputOption::VALUE_OPTIONAL, 'Database user')
62
            ->addOption('dbPass', null, InputOption::VALUE_OPTIONAL, 'Database password')
63
            ->addOption('dbName', null, InputOption::VALUE_OPTIONAL, 'Database name')
64
            ->addOption('dbPort', null, InputOption::VALUE_OPTIONAL, 'Database port', 3306)
65
            ->addOption('dbPrefix', null, InputOption::VALUE_OPTIONAL, 'Table prefix', '')
66
            ->addOption('installSampleData', null, InputOption::VALUE_OPTIONAL, 'Install sample data')
67
            ->addOption('useDefaultConfigParams', null, InputOption::VALUE_OPTIONAL, 'Use default installation parameters defined in the yaml file')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 148 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
68
            ->addOption('baseUrl', null, InputOption::VALUE_OPTIONAL, 'Installation base url')
69
            ->addOption('replaceHtaccessFile', null, InputOption::VALUE_OPTIONAL, 'Generate htaccess file (for non vhost environment)')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 135 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
70
            ->addOption(
71
                'noDownload',
72
                null,
73
                InputOption::VALUE_NONE,
74
                'If set skips download step. Used when installationFolder is already a Magento installation that has ' .
75
                'to be installed on the given database.'
76
            )
77
            ->addOption(
78
                'only-download',
79
                null,
80
                InputOption::VALUE_NONE,
81
                'Downloads (and extracts) source code'
82
            )
83
            ->addOption('forceUseDb', null, InputOption::VALUE_OPTIONAL, 'If --noDownload passed, force to use given database if it already exists.')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 149 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
84
            ->setDescription('Install magento')
85
        ;
86
87
        $help = <<<HELP
88
* Download Magento by a list of git repos and zip files (mageplus, magelte, official community packages).
89
* Try to create database if it does not exist.
90
* Installs Magento sample data if available (since version 1.2.0).
91
* Starts Magento installer
92
* Sets rewrite base in .htaccess file
93
94
Example of an unattended Magento CE 1.7.0.2 installation:
95
96
   $ n98-magerun.phar install --dbHost="localhost" --dbUser="mydbuser" --dbPass="mysecret" --dbName="magentodb" --installSampleData=yes --useDefaultConfigParams=yes --magentoVersionByName="magento-ce-1.7.0.2" --installationFolder="magento" --baseUrl="http://magento.localdomain/"
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 279 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
97
98
Additionally, with --noDownload option you can install Magento working copy already stored in --installationFolder on
99
the given database.
100
101
See it in action: http://youtu.be/WU-CbJ86eQc
102
103
HELP;
104
        $this->setHelp($help);
105
106
        $this->notEmptyCallback = function($input)
107
        {
108
            if (empty($input)) {
109
                throw new InvalidArgumentException('Please enter a value');
110
            }
111
            return $input;
112
        };
113
    }
114
115
    /**
116
     * @param InputInterface $input
117
     * @param OutputInterface $output
118
     * @throws RuntimeException
119
     * @return int|null|void
120
     */
121
    protected function execute(InputInterface $input, OutputInterface $output)
122
    {
123
        $this->commandConfig = $this->getCommandConfig();
124
        $this->writeSection($output, 'Magento Installation');
125
126
        $this->precheckPhp();
127
128
        if (!$input->getOption('noDownload')) {
129
            $this->selectMagentoVersion($input, $output);
130
        }
131
132
        $this->chooseInstallationFolder($input, $output);
133
134
        if (!$input->getOption('noDownload')) {
135
            $result = $this->downloadMagento($input, $output);
136
137
            if ($result === false) {
138
                return 1;
139
            }
140
        }
141
142
        if ($input->getOption('only-download')) {
143
            return 0;
144
        }
145
146
        $this->createDatabase($input, $output);
147
148
        if (!$input->getOption('noDownload')) {
149
            $this->installSampleData($input, $output);
150
        }
151
152
        $this->removeEmptyFolders();
153
        $this->setDirectoryPermissions($output);
154
        $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...
155
    }
156
157
    /**
158
     * Check PHP environment agains minimal required settings modules
159
     */
160
    protected function precheckPhp()
161
    {
162
        $extensions = $this->commandConfig['installation']['pre-check']['php']['extensions'];
163
        $missingExtensions = array();
164
        foreach ($extensions as $extension) {
165
            if (!extension_loaded($extension)) {
166
                $missingExtensions[] = $extension;
167
            }
168
        }
169
170
        if (count($missingExtensions) > 0) {
171
            throw new RuntimeException(
172
                'The following PHP extensions are required to start installation: ' . implode(',', $missingExtensions)
173
            );
174
        }
175
    }
176
177
    /**
178
     * @param InputInterface  $input
179
     * @param OutputInterface $output
180
     *
181
     * @throws InvalidArgumentException
182
     */
183
    protected function selectMagentoVersion(InputInterface $input, OutputInterface $output)
184
    {
185
        if ($input->getOption('magentoVersion') == null && $input->getOption('magentoVersionByName') == null) {
186
            $question = array();
187
            foreach ($this->commandConfig['magento-packages'] as $key => $package) {
188
                $question[] = '<comment>' . str_pad('[' . ($key + 1) . ']', 4, ' ') . '</comment> ' . $package['name'] . "\n";
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
189
            }
190
            $question[] = "<question>Choose a magento version:</question> ";
191
192
            $commandConfig = $this->commandConfig;
193
194
195
            $type = $this->getHelper('dialog')->askAndValidate($output, $question, function($typeInput) use ($commandConfig) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
196
                if (!in_array($typeInput, range(1, count($commandConfig['magento-packages'])))) {
197
                    throw new InvalidArgumentException('Invalid type');
198
                }
199
200
                return $typeInput;
201
            });
202
        } else {
203
            $type = null;
204
205
            if ($input->getOption('magentoVersion')) {
206
                $type = $input->getOption('magentoVersion');
207
                if ($type !== (string)(int)$type) {
208
                    $type = $this->getPackageNumberByName($type);
209
                }
210
            } elseif ($input->getOption('magentoVersionByName')) {
211
                $type = $this->getPackageNumberByName($input->getOption('magentoVersionByName'));
212
            }
213
214
            if ($type == null) {
215
                throw new InvalidArgumentException('Unable to locate Magento version');
216
            }
217
        }
218
219
        $magentoPackages = $this->commandConfig['magento-packages'];
220
221
        $index = $type - 1;
222
        if (!isset($magentoPackages[$index])) {
223
            throw new InvalidArgumentException(
224
                sprintf(
225
                    'Invalid Magento package number %s, must be from 1 to %d.', var_export($type, true),
226
                    count($magentoPackages)
227
                )
228
            );
229
        }
230
231
        $this->config['magentoVersionData'] = $magentoPackages[$index];
232
    }
233
234
235
    /**
236
     * @param $name
237
     *
238
     * @return int 1 or greater as the one-based package number, null on failure to resolve the name
239
     */
240
    private function getPackageNumberByName($name)
241
    {
242
        // directly filter integer strings
243
        if ($name === (string)(int)$name) {
244
            return (int) $name;
245
        }
246
247
        $magentoPackages = $this->commandConfig['magento-packages'];
248
249
        foreach ($magentoPackages as $key => $package) {
250
            if ($package['name'] === $name) {
251
                return $key + 1;
252
            }
253
        }
254
255
        return null;
256
    }
257
258
    /**
259
     * @param InputInterface $input
260
     * @param OutputInterface $output
261
     * @return bool
262
     */
263
    public function downloadMagento(InputInterface $input, OutputInterface $output)
264
    {
265
        try {
266
            $package = $this->createComposerPackageByConfig($this->config['magentoVersionData']);
267
            $this->config['magentoPackage'] = $package;
268
269
            if (file_exists($this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'Mage.php')) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 132 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
270
                $output->writeln('<error>A magento installation already exists in this folder </error>');
271
                return false;
272
            }
273
274
            $composer = $this->getComposer($input, $output);
275
            $targetFolder = $this->getTargetFolderByType($composer, $package, $this->config['installationFolder']);
276
            $this->config['magentoPackage'] = $this->downloadByComposerConfig(
277
                $input,
278
                $output,
279
                $package,
280
                $targetFolder,
281
                true
282
            );
283
284
            if ($this->isSourceTypeRepository($package->getSourceType())) {
285
                $filesystem = new \N98\Util\Filesystem;
286
                $filesystem->recursiveCopy($targetFolder, $this->config['installationFolder'], array('.git', '.hg'));
287
            } else {
288
                $filesystem = new \Composer\Util\Filesystem();
289
                $filesystem->copyThenRemove(
290
                    $this->config['installationFolder'] . '/_n98_magerun_download', $this->config['installationFolder']
291
                );
292
            }
293
294
            if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
295
                // Patch installer
296
                $this->patchMagentoInstallerForPHP54($this->config['installationFolder']);
297
            }
298
        } catch (Exception $e) {
299
            $output->writeln('<error>' . $e->getMessage() . '</error>');
300
            return false;
301
        }
302
303
        return true;
304
    }
305
306
    /**
307
     * construct a folder to where magerun will download the source to, cache git/hg repositories under COMPOSER_HOME
308
     *
309
     * @param Composer $composer
310
     * @param CompletePackage $package
311
     * @param $installationFolder
312
     *
313
     * @return string
314
     */
315
    protected function getTargetFolderByType(Composer $composer, CompletePackage $package, $installationFolder)
316
    {
317
        $type = $package->getSourceType();
318
        if ($this->isSourceTypeRepository($type)) {
319
            $targetPath = sprintf(
320
                '%s/%s/%s/%s',
321
                $composer->getConfig()->get('cache-dir'),
322
                '_n98_magerun_download',
323
                $type,
324
                preg_replace('{[^a-z0-9.]}i', '-', $package->getSourceUrl())
325
            );
326
        } else {
327
            $targetPath = sprintf(
328
                '%s/%s',
329
                $installationFolder,
330
                '_n98_magerun_download'
331
            );
332
        }
333
        return $targetPath;
334
    }
335
336
    /**
337
     * @param string $magentoFolder
338
     */
339
    protected function patchMagentoInstallerForPHP54($magentoFolder)
340
    {
341
        $installerConfig = $magentoFolder
342
            . DIRECTORY_SEPARATOR
343
            . 'app/code/core/Mage/Install/etc/config.xml';
344
        if (file_exists($installerConfig)) {
345
            $xml = file_get_contents($installerConfig);
346
            file_put_contents($installerConfig, str_replace('<pdo_mysql/>', '<pdo_mysql>1</pdo_mysql>', $xml));
347
        }
348
    }
349
350
    /**
351
     * @param InputInterface  $input
352
     * @param OutputInterface $output
353
     *
354
     * @throws InvalidArgumentException
355
     */
356
    protected function createDatabase(InputInterface $input, OutputInterface $output)
357
    {
358
        $dbOptions = array('--dbHost', '--dbUser', '--dbPass', '--dbName');
359
        $dbOptionsFound = 0;
360
        foreach ($dbOptions as $dbOption) {
361
            foreach ($this->getCliArguments() as $definedCliOption) {
362
                if (BinaryString::startsWith($definedCliOption, $dbOption)) {
363
                    $dbOptionsFound++;
364
                }
365
            }
366
        }
367
368
        $hasAllOptions = $dbOptionsFound == 4;
369
370
        // if all database options were passed in at cmd line
371
        if ($hasAllOptions) {
372
            $this->config['db_host'] = $input->getOption('dbHost');
373
            $this->config['db_user'] = $input->getOption('dbUser');
374
            $this->config['db_pass'] = $input->getOption('dbPass');
375
            $this->config['db_name'] = $input->getOption('dbName');
376
            $this->config['db_port'] = $input->getOption('dbPort');
377
            $this->config['db_prefix'] = $input->getOption('dbPrefix');
378
            $db = $this->validateDatabaseSettings($output, $input);
379
380
            if ($db === false) {
381
                throw new InvalidArgumentException("Database configuration is invalid");
382
            }
383
384
        } else {
385
            $dialog = $this->getHelperSet()->get('dialog');
386
            do {
387
                $dbHostDefault = $input->getOption('dbHost') ? $input->getOption('dbHost') : 'localhost';
388
                $this->config['db_host'] = $dialog->askAndValidate($output, '<question>Please enter the database host</question> <comment>[' . $dbHostDefault . ']</comment>: ', $this->notEmptyCallback, false, $dbHostDefault);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 225 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
389
390
                $dbUserDefault = $input->getOption('dbUser') ? $input->getOption('dbUser') : 'root';
391
                $this->config['db_user'] = $dialog->askAndValidate($output, '<question>Please enter the database username</question> <comment>[' . $dbUserDefault . ']</comment>: ', $this->notEmptyCallback, false, $dbUserDefault);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 229 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
392
393
                $dbPassDefault = $input->getOption('dbPass') ? $input->getOption('dbPass') : '';
394
                $this->config['db_pass'] = $dialog->ask($output, '<question>Please enter the database password</question> <comment>[' . $dbPassDefault . ']</comment>: ', $dbPassDefault);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 186 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
395
396
                $dbNameDefault = $input->getOption('dbName') ? $input->getOption('dbName') : 'magento';
397
                $this->config['db_name'] = $dialog->askAndValidate($output, '<question>Please enter the database name</question> <comment>[' . $dbNameDefault . ']</comment>: ', $this->notEmptyCallback, false, $dbNameDefault);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 225 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
398
399
                $dbPortDefault = $input->getOption('dbPort') ? $input->getOption('dbPort') : 3306;
400
                $this->config['db_port'] = $dialog->askAndValidate($output, '<question>Please enter the database port </question> <comment>[' . $dbPortDefault . ']</comment>: ', $this->notEmptyCallback, false, $dbPortDefault);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 226 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
401
402
                $dbPrefixDefault = $input->getOption('dbPrefix') ? $input->getOption('dbPrefix') : '';
403
                $this->config['db_prefix'] = $dialog->ask($output, '<question>Please enter the table prefix</question> <comment>[' . $dbPrefixDefault . ']</comment>:', $dbPrefixDefault);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 186 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
404
                $db = $this->validateDatabaseSettings($output, $input);
405
            } while ($db === false);
406
        }
407
408
        $this->config['db'] = $db;
409
    }
410
411
    /**
412
     * @param OutputInterface $output
413
     * @param InputInterface  $input
414
     *
415
     * @return bool|PDO
416
     */
417
    protected function validateDatabaseSettings(OutputInterface $output, InputInterface $input)
418
    {
419
        try {
420
            $dsn = sprintf("mysql:host=%s;port=%s", $this->config['db_host'], $this->config['db_port']);
421
            $db = new PDO($dsn, $this->config['db_user'], $this->config['db_pass']);
422
            if (!$db->query('USE ' . $this->config['db_name'])) {
423
                $db->query("CREATE DATABASE `" . $this->config['db_name'] . "`");
424
                $output->writeln('<info>Created database ' . $this->config['db_name'] . '</info>');
425
                $db->query('USE ' . $this->config['db_name']);
426
427
                return $db;
428
            }
429
430
            if ($input->getOption('noDownload') && !$input->getOption('forceUseDb')) {
431
                $output->writeln("<error>Database {$this->config['db_name']} already exists.</error>");
432
433
                return false;
434
            }
435
436
            return $db;
437
        } catch (PDOException $e) {
438
            $output->writeln('<error>' . $e->getMessage() . '</error>');
439
        }
440
441
        return false;
442
    }
443
444
    /**
445
     * @param InputInterface $input
446
     * @param OutputInterface $output
447
     */
448
    protected function installSampleData(InputInterface $input, OutputInterface $output)
449
    {
450
        $magentoPackage = $this->config['magentoPackage']; /* @var $magentoPackage \Composer\Package\MemoryPackage */
451
        $extra = $magentoPackage->getExtra();
452
        if (!isset($extra['sample-data'])) {
453
            return;
454
        }
455
456
        $dialog = $this->getHelperSet()->get('dialog');
457
458
        $installSampleData = ($input->getOption('installSampleData') !== null) ? $this->_parseBoolOption($input->getOption('installSampleData')) : $dialog->askConfirmation($output, '<question>Install sample data?</question> <comment>[y]</comment>: ');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 251 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
459
460
        if ($installSampleData) {
461
            $filesystem = new Filesystem();
462
463
            foreach ($this->commandConfig['demo-data-packages'] as $demoPackageData) {
464
                if ($demoPackageData['name'] == $extra['sample-data']) {
465
                    $package = $this->downloadByComposerConfig(
466
                        $input,
467
                        $output,
468
                        $demoPackageData,
469
                        $this->config['installationFolder'] . '/_temp_demo_data',
470
                        false
471
                    );
472
473
                    $this->_fixComposerExtractionBug();
474
475
                    $expandedFolder = $this->config['installationFolder']
476
                                    . '/_temp_demo_data/'
477
                                    . str_replace(array('.tar.gz', '.tar.bz2', '.zip'), '', basename($package->getDistUrl()));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
478
                    if (is_dir($expandedFolder)) {
479
                        $filesystem->recursiveCopy(
480
                            $expandedFolder,
481
                            $this->config['installationFolder']
482
                        );
483
                        $filesystem->recursiveRemoveDirectory($expandedFolder);
484
                    }
485
486
                    // Remove empty folder
487 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...
488
                        $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/vendor/composer');
489
                    }
490
491
                    // Install sample data
492
                    $sampleDataSqlFile = glob($this->config['installationFolder'] . '/_temp_demo_data/magento_*sample_data*sql');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 129 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
493
                    $db = $this->config['db'];
494
                    /* @var $db PDO */
495
                    if (isset($sampleDataSqlFile[0])) {
496
                        if (OperatingSystem::isProgramInstalled('mysql')) {
497
                            $exec = 'mysql '
498
                                . '-h' . escapeshellarg(strval($this->config['db_host']))
499
                                . ' '
500
                                . '-u' . escapeshellarg(strval($this->config['db_user']))
501
                                . ' '
502
                                . ($this->config['db_port'] != '3306' ? '-P' . escapeshellarg($this->config['db_port']) . ' ' : '')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 131 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
503
                                . (!strval($this->config['db_pass'] == '') ? '-p' . escapeshellarg($this->config['db_pass']) . ' ' : '')
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
504
                                . strval($this->config['db_name'])
505
                                . ' < '
506
                                . escapeshellarg($sampleDataSqlFile[0]);
507
                            $output->writeln('<info>Importing <comment>' . $sampleDataSqlFile[0] . '</comment> with mysql cli client</info>');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 142 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
508
                            Exec::run($exec);
509
                            @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...
510
                        } else {
511
                            $output->writeln('<info>Importing <comment>' . $sampleDataSqlFile[0] . '</comment> with PDO driver</info>');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
512
                            // Fallback -> Try to install dump file by PDO driver
513
                            $dbUtils = new DatabaseUtils();
514
                            $dbUtils->importSqlDump($db, $sampleDataSqlFile[0]);
515
                        }
516
                    }
517
                }
518
            }
519
520 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...
521
                $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/_temp_demo_data');
522
            }
523
        }
524
    }
525
526
    protected function _fixComposerExtractionBug()
527
    {
528
        $filesystem = new Filesystem();
529
        foreach (array('/_temp_demo_data/media' => '/media', '/_temp_demo_data/skin' => '/skin') as $wrong => $right) {
530
            $wrongFolder = $this->config['installationFolder'] . $wrong;
531
            $rightFolder = $this->config['installationFolder'] . $right;
532
            if (is_dir($wrongFolder)) {
533
                $filesystem->recursiveCopy(
534
                    $wrongFolder,
535
                    $rightFolder
536
                );
537
                $filesystem->recursiveRemoveDirectory($wrongFolder);
538
            }
539
        }
540
    }
541
542
    /**
543
     * Remove empty composer extraction folder
544
     */
545
    protected function removeEmptyFolders()
546
    {
547
        if (is_dir(getcwd() . '/vendor')) {
548
            $finder = new Finder();
549
            $finder->files()->depth(3)->in(getcwd() . '/vendor');
550
            if ($finder->count() == 0) {
551
                $filesystem = new Filesystem();
552
                $filesystem->recursiveRemoveDirectory(getcwd() . '/vendor');
553
            }
554
        }
555
    }
556
557
    /**
558
     * @param InputInterface  $input
559
     * @param OutputInterface $output
560
     *
561
     * @return array
562
     * @throws InvalidArgumentException parameter mismatch (e.g. base-url components like hostname)
563
     * @throws RuntimeException
564
     */
565
    protected function installMagento(InputInterface $input, OutputInterface $output)
566
    {
567
        $this->getApplication()->setAutoExit(false);
568
        $dialog = $this->getHelperSet()->get('dialog');
569
570
        $defaults = $this->commandConfig['installation']['defaults'];
571
572
        $useDefaultConfigParams = $this->_parseBoolOption($input->getOption('useDefaultConfigParams'));
573
574
        $sessionSave = $useDefaultConfigParams ? $defaults['session_save'] : $dialog->ask(
575
            $output,
576
            '<question>Please enter the session save:</question> <comment>[' . $defaults['session_save'] . ']</comment>: ',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 123 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
577
            $defaults['session_save']
578
        );
579
580
        $adminFrontname = $useDefaultConfigParams ? $defaults['admin_frontname'] : $dialog->askAndValidate(
581
            $output,
582
            '<question>Please enter the admin frontname:</question> <comment>[' . $defaults['admin_frontname'] . ']</comment> ',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
583
            $this->notEmptyCallback,
584
            false,
585
            $defaults['admin_frontname']
586
        );
587
588
        $currency = $useDefaultConfigParams ? $defaults['currency'] : $dialog->askAndValidate(
589
            $output,
590
            '<question>Please enter the default currency code:</question> <comment>[' . $defaults['currency'] . ']</comment>: ',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
591
            $this->notEmptyCallback,
592
            false,
593
            $defaults['currency']
594
        );
595
596
        $locale = $useDefaultConfigParams ? $defaults['locale'] : $dialog->askAndValidate(
597
            $output,
598
            '<question>Please enter the locale code:</question> <comment>[' . $defaults['locale'] . ']</comment>: ',
599
            $this->notEmptyCallback,
600
            false,
601
            $defaults['locale']
602
        );
603
604
        $timezone = $useDefaultConfigParams ? $defaults['timezone'] : $dialog->askAndValidate(
605
            $output,
606
            '<question>Please enter the timezone:</question> <comment>[' . $defaults['timezone'] . ']</comment>: ',
607
            $this->notEmptyCallback,
608
            false,
609
            $defaults['timezone']
610
        );
611
612
        $adminUsername = $useDefaultConfigParams ? $defaults['admin_username'] : $dialog->askAndValidate(
613
            $output,
614
            '<question>Please enter the admin username:</question> <comment>[' . $defaults['admin_username'] . ']</comment>: ',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 127 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
615
            $this->notEmptyCallback,
616
            false,
617
            $defaults['admin_username']
618
        );
619
620
        $adminPassword = $useDefaultConfigParams ? $defaults['admin_password'] : $dialog->askAndValidate(
621
            $output,
622
            '<question>Please enter the admin password:</question> <comment>[' . $defaults['admin_password'] . ']</comment>: ',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 127 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
623
            $this->notEmptyCallback,
624
            false,
625
            $defaults['admin_password']
626
        );
627
628
        $adminFirstname = $useDefaultConfigParams ? $defaults['admin_firstname'] : $dialog->askAndValidate(
629
            $output,
630
            '<question>Please enter the admin\'s firstname:</question> <comment>[' . $defaults['admin_firstname'] . ']</comment>: ',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 132 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
631
            $this->notEmptyCallback,
632
            false,
633
            $defaults['admin_firstname']
634
        );
635
636
        $adminLastname = $useDefaultConfigParams ? $defaults['admin_lastname'] : $dialog->askAndValidate(
637
            $output,
638
            '<question>Please enter the admin\'s lastname:</question> <comment>[' . $defaults['admin_lastname'] . ']</comment>: ',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
639
            $this->notEmptyCallback,
640
            false,
641
            $defaults['admin_lastname']
642
        );
643
644
        $adminEmail = $useDefaultConfigParams ? $defaults['admin_email'] : $dialog->askAndValidate(
645
            $output,
646
            '<question>Please enter the admin\'s email:</question> <comment>[' . $defaults['admin_email'] . ']</comment>: ',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
647
            $this->notEmptyCallback,
648
            false,
649
            $defaults['admin_email']
650
        );
651
652
        $validateBaseUrl = function($input) {
653
            if (!preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $input)) {
654
                throw new InvalidArgumentException('Please enter a valid URL');
655
            }
656
            if (parse_url($input, \PHP_URL_HOST) == 'localhost') {
657
                throw new InvalidArgumentException('localhost cause problems! Please use 127.0.0.1 or another hostname');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
658
            }
659
            return $input;
660
        };
661
662
        $baseUrl = ($input->getOption('baseUrl') !== null) ? $input->getOption('baseUrl') : $dialog->askAndValidate(
663
            $output,
664
            '<question>Please enter the base url:</question> ',
665
            $validateBaseUrl,
666
            false
667
        );
668
        $baseUrl = rtrim($baseUrl, '/') . '/'; // normalize baseUrl
669
670
        /**
671
         * Correct session save (common mistake)
672
         */
673
        if ($sessionSave == 'file') {
674
            $sessionSave = 'files';
675
        }
676
677
        /**
678
         * Try to create session folder
679
         */
680
        $defaultSessionFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/session';
681
        if ($sessionSave == 'files' && !is_dir($defaultSessionFolder)) {
682
            @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...
683
        }
684
685
        $dbHost = $this->config['db_host'];
686
        if ($this->config['db_port'] != 3306) {
687
            $dbHost .= ':' . $this->config['db_port'];
688
        }
689
690
        $argv = array(
691
            'license_agreement_accepted' => 'yes',
692
            'locale'                     => $locale,
693
            'timezone'                   => $timezone,
694
            'db_host'                    => $dbHost,
695
            'db_name'                    => $this->config['db_name'],
696
            'db_user'                    => $this->config['db_user'],
697
            'db_pass'                    => $this->config['db_pass'],
698
            'db_prefix'                  => $this->config['db_prefix'],
699
            'url'                        => $baseUrl,
700
            'use_rewrites'               => 'yes',
701
            'use_secure'                 => 'no',
702
            'secure_base_url'            => '',
703
            'use_secure_admin'           => 'no',
704
            'admin_username'             => $adminUsername,
705
            'admin_lastname'             => $adminLastname,
706
            'admin_firstname'            => $adminFirstname,
707
            'admin_email'                => $adminEmail,
708
            'admin_password'             => $adminPassword,
709
            'session_save'               => $sessionSave,
710
            'admin_frontname'            => $adminFrontname, /* magento 1 */
711
            'backend_frontname'          => $adminFrontname, /* magento 2 */
712
            'default_currency'           => $currency,
713
            'skip_url_validation'        => 'yes',
714
        );
715
        if ($useDefaultConfigParams) {
716
            if (strlen($defaults['encryption_key']) > 0) {
717
                $argv['encryption_key'] = $defaults['encryption_key'];
718
            }
719
            if (strlen($defaults['use_secure']) > 0) {
720
                $argv['use_secure'] = $defaults['use_secure'];
721
                $argv['secure_base_url'] = str_replace('http://', 'https://', $baseUrl);
722
            }
723
            if (strlen($defaults['use_rewrites']) > 0) {
724
                $argv['use_rewrites'] = $defaults['use_rewrites'];
725
            }
726
        }
727
        $installArgs = '';
728
        foreach ($argv as $argName => $argValue) {
729
            $installArgs .= '--' . $argName . ' ' . escapeshellarg($argValue) . ' ';
730
        }
731
732
        $output->writeln('<info>Start installation process.</info>');
733
734
        $phpExec = $this->getPhpExecutable();
735
        $installCommand = $phpExec . ' -f ' . escapeshellarg($this->getInstallScriptPath()) . ' -- ' . $installArgs;
736
737
738
        $output->writeln('<comment>' . $installCommand . '</comment>');
739
        Exec::run($installCommand, $installationOutput, $returnStatus);
740
        if ($returnStatus !== self::EXEC_STATUS_OK) {
741
            $this->getApplication()->setAutoExit(true);
742
            throw new RuntimeException('Installation failed.' . $installationOutput, 1);
743
        } else {
744
            $output->writeln('<info>Successfully installed Magento</info>');
745
            $encryptionKey = trim(substr($installationOutput, strpos($installationOutput, ':') + 1));
746
            $output->writeln('<comment>Encryption Key:</comment> <info>' . $encryptionKey . '</info>');
747
        }
748
749
        $dialog = $this->getHelperSet()->get('dialog');
750
751
        /**
752
         * Htaccess file
753
         */
754
        if ($input->getOption('useDefaultConfigParams') == null || $input->getOption('replaceHtaccessFile') != null) {
755
            $replaceHtaccessFile = false;
756
757
            if ($this->_parseBoolOption($input->getOption('replaceHtaccessFile'))) {
758
                $replaceHtaccessFile = true;
759
            } elseif ($dialog->askConfirmation(
760
                $output,
761
                '<question>Write BaseURL to .htaccess file?</question> <comment>[n]</comment>: ',
762
                false)
763
            ) {
764
                $replaceHtaccessFile = true;
765
            }
766
767
            if ($replaceHtaccessFile) {
768
                $this->replaceHtaccessFile($baseUrl);
769
            }
770
        }
771
772
        \chdir($this->config['installationFolder']);
773
        $this->getApplication()->reinit();
774
        $output->writeln('<info>Reindex all after installation</info>');
775
        $this->getApplication()->run(new StringInput('index:reindex:all'), $output);
776
        $this->getApplication()->run(new StringInput('sys:check'), $output);
777
        $output->writeln('<info>Successfully installed magento</info>');
778
    }
779
780
    /**
781
     * Check if we have a magento 2 or 1 installation and return path to install.php
782
     *
783
     * @return string
784
     */
785
    protected function getInstallScriptPath()
786
    {
787
        $magento1InstallScriptPath  = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'install.php';
788
        $magento2InstallScriptPath  = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'dev/shell/install.php';
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
789
        if (file_exists($magento2InstallScriptPath)) {
790
            return $magento2InstallScriptPath;
791
        }
792
793
        return $magento1InstallScriptPath;
794
    }
795
796
    /**
797
     * @param string $baseUrl
798
     */
799
    protected function replaceHtaccessFile($baseUrl)
800
    {
801
        $content = file_get_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess');
802
        copy($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess', $this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess.dist');
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 164 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
803
        $content = str_replace('#RewriteBase /magento/', 'RewriteBase ' . parse_url($baseUrl, PHP_URL_PATH), $content);
804
        file_put_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess', $content);
805
    }
806
807
    /**
808
     * @param OutputInterface $output
809
     */
810
    protected function setDirectoryPermissions($output)
811
    {
812
        try {
813
            $varFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var';
814
            if (!is_dir($varFolder)) {
815
                @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...
816
            }
817
            @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...
818
819
            $varCacheFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/cache';
820
            if (!is_dir($varCacheFolder)) {
821
                @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...
822
            }
823
            @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...
824
825
            $mediaFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'media';
826
            if (!is_dir($mediaFolder)) {
827
                @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...
828
            }
829
            @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...
830
831
            $finder = Finder::create();
832
            $finder->directories()
833
                ->ignoreUnreadableDirs(true)
834
                ->in(array($varFolder, $mediaFolder));
835
            foreach ($finder as $dir) {
836
                @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...
837
            }
838
        } catch (Exception $e) {
839
            $output->writeln('<error>' . $e->getMessage() . '</error>');
840
        }
841
    }
842
843
844
    /**
845
     * Retrieve path to php executable
846
     *
847
     * @return string
848
     */
849
    protected function getPhpExecutable()
850
    {
851
        $php = getenv('_');
852
        if(!$php) {
853
            if (OperatingSystem::isWindows()) {
854
                $php = 'php';
855
            } else {
856
                $php = '/usr/bin/env php';
857
            }
858
        }
859
        return $php;
860
    }
861
862
863
    /**
864
     * @return array
865
     */
866
    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...
867
    {
868
        if ($this->_argv === null) {
869
            $this->_argv = $_SERVER['argv'];
870
        }
871
872
        return $this->_argv;
873
    }
874
875
    /**
876
     * @param array $args
877
     */
878
    public function setCliArguments($args)
879
    {
880
        $this->_argv = $args;
881
    }
882
}
883