Completed
Push — master ( bd5921...d9293e )
by Christian
17:54 queued 08:59
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\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
     * @return bool
117
     */
118
    public function isEnabled()
119
    {
120
        return Exec::allowed();
121
    }
122
123
    /**
124
     * @param InputInterface $input
125
     * @param OutputInterface $output
126
     * @throws RuntimeException
127
     * @return int|null|void
128
     */
129
    protected function execute(InputInterface $input, OutputInterface $output)
130
    {
131
        $this->commandConfig = $this->getCommandConfig();
132
        $this->writeSection($output, 'Magento Installation');
133
134
        $this->precheckPhp();
135
136
        if (!$input->getOption('noDownload')) {
137
            $this->selectMagentoVersion($input, $output);
138
        }
139
140
        $this->chooseInstallationFolder($input, $output);
141
142
        if (!$input->getOption('noDownload')) {
143
            $result = $this->downloadMagento($input, $output);
144
145
            if ($result === false) {
146
                return 1;
147
            }
148
        }
149
150
        if ($input->getOption('only-download')) {
151
            return 0;
152
        }
153
154
        $this->createDatabase($input, $output);
155
156
        if (!$input->getOption('noDownload')) {
157
            $this->installSampleData($input, $output);
158
        }
159
160
        $this->removeEmptyFolders();
161
        $this->setDirectoryPermissions($output);
162
        $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...
163
    }
164
165
    /**
166
     * Check PHP environment agains minimal required settings modules
167
     */
168
    protected function precheckPhp()
169
    {
170
        $extensions = $this->commandConfig['installation']['pre-check']['php']['extensions'];
171
        $missingExtensions = array();
172
        foreach ($extensions as $extension) {
173
            if (!extension_loaded($extension)) {
174
                $missingExtensions[] = $extension;
175
            }
176
        }
177
178
        if (count($missingExtensions) > 0) {
179
            throw new RuntimeException(
180
                'The following PHP extensions are required to start installation: ' . implode(',', $missingExtensions)
181
            );
182
        }
183
    }
184
185
    /**
186
     * @param InputInterface  $input
187
     * @param OutputInterface $output
188
     *
189
     * @throws InvalidArgumentException
190
     */
191
    protected function selectMagentoVersion(InputInterface $input, OutputInterface $output)
192
    {
193
        if ($input->getOption('magentoVersion') == null && $input->getOption('magentoVersionByName') == null) {
194
            $question = array();
195
            foreach ($this->commandConfig['magento-packages'] as $key => $package) {
196
                $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...
197
            }
198
            $question[] = "<question>Choose a magento version:</question> ";
199
200
            $commandConfig = $this->commandConfig;
201
202
203
            $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...
204
                if (!in_array($typeInput, range(1, count($commandConfig['magento-packages'])))) {
205
                    throw new InvalidArgumentException('Invalid type');
206
                }
207
208
                return $typeInput;
209
            });
210
        } else {
211
            $type = null;
212
213
            if ($input->getOption('magentoVersion')) {
214
                $type = $input->getOption('magentoVersion');
215
                if ($type !== (string) (int) $type) {
216
                    $type = $this->getPackageNumberByName($type);
217
                }
218
            } elseif ($input->getOption('magentoVersionByName')) {
219
                $type = $this->getPackageNumberByName($input->getOption('magentoVersionByName'));
220
            }
221
222
            if ($type == null) {
223
                throw new InvalidArgumentException('Unable to locate Magento version');
224
            }
225
        }
226
227
        $magentoPackages = $this->commandConfig['magento-packages'];
228
229
        $index = $type - 1;
230
        if (!isset($magentoPackages[$index])) {
231
            throw new InvalidArgumentException(
232
                sprintf(
233
                    'Invalid Magento package number %s, must be from 1 to %d.', var_export($type, true),
234
                    count($magentoPackages)
235
                )
236
            );
237
        }
238
239
        $this->config['magentoVersionData'] = $magentoPackages[$index];
240
    }
241
242
243
    /**
244
     * @param $name
245
     *
246
     * @return int 1 or greater as the one-based package number, null on failure to resolve the name
247
     */
248
    private function getPackageNumberByName($name)
249
    {
250
        // directly filter integer strings
251
        if ($name === (string) (int) $name) {
252
            return (int) $name;
253
        }
254
255
        $magentoPackages = $this->commandConfig['magento-packages'];
256
257
        foreach ($magentoPackages as $key => $package) {
258
            if ($package['name'] === $name) {
259
                return $key + 1;
260
            }
261
        }
262
263
        return null;
264
    }
265
266
    /**
267
     * @param InputInterface $input
268
     * @param OutputInterface $output
269
     * @return bool
270
     */
271
    public function downloadMagento(InputInterface $input, OutputInterface $output)
272
    {
273
        try {
274
            $package = $this->createComposerPackageByConfig($this->config['magentoVersionData']);
275
            $this->config['magentoPackage'] = $package;
276
277
            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...
278
                $output->writeln('<error>A magento installation already exists in this folder </error>');
279
                return false;
280
            }
281
282
            $composer = $this->getComposer($input, $output);
283
            $targetFolder = $this->getTargetFolderByType($composer, $package, $this->config['installationFolder']);
284
            $this->config['magentoPackage'] = $this->downloadByComposerConfig(
285
                $input,
286
                $output,
287
                $package,
288
                $targetFolder,
289
                true
290
            );
291
292
            if ($this->isSourceTypeRepository($package->getSourceType())) {
293
                $filesystem = new \N98\Util\Filesystem;
294
                $filesystem->recursiveCopy($targetFolder, $this->config['installationFolder'], array('.git', '.hg'));
295
            } else {
296
                $filesystem = new \Composer\Util\Filesystem();
297
                $filesystem->copyThenRemove(
298
                    $this->config['installationFolder'] . '/_n98_magerun_download', $this->config['installationFolder']
299
                );
300
            }
301
302
            if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
303
                // Patch installer
304
                $this->patchMagentoInstallerForPHP54($this->config['installationFolder']);
305
            }
306
        } catch (Exception $e) {
307
            $output->writeln('<error>' . $e->getMessage() . '</error>');
308
            return false;
309
        }
310
311
        return true;
312
    }
313
314
    /**
315
     * construct a folder to where magerun will download the source to, cache git/hg repositories under COMPOSER_HOME
316
     *
317
     * @param Composer $composer
318
     * @param CompletePackage $package
319
     * @param $installationFolder
320
     *
321
     * @return string
322
     */
323
    protected function getTargetFolderByType(Composer $composer, CompletePackage $package, $installationFolder)
324
    {
325
        $type = $package->getSourceType();
326
        if ($this->isSourceTypeRepository($type)) {
327
            $targetPath = sprintf(
328
                '%s/%s/%s/%s',
329
                $composer->getConfig()->get('cache-dir'),
330
                '_n98_magerun_download',
331
                $type,
332
                preg_replace('{[^a-z0-9.]}i', '-', $package->getSourceUrl())
333
            );
334
        } else {
335
            $targetPath = sprintf(
336
                '%s/%s',
337
                $installationFolder,
338
                '_n98_magerun_download'
339
            );
340
        }
341
        return $targetPath;
342
    }
343
344
    /**
345
     * @param string $magentoFolder
346
     */
347
    protected function patchMagentoInstallerForPHP54($magentoFolder)
348
    {
349
        $installerConfig = $magentoFolder
350
            . DIRECTORY_SEPARATOR
351
            . 'app/code/core/Mage/Install/etc/config.xml';
352
        if (file_exists($installerConfig)) {
353
            $xml = file_get_contents($installerConfig);
354
            file_put_contents($installerConfig, str_replace('<pdo_mysql/>', '<pdo_mysql>1</pdo_mysql>', $xml));
355
        }
356
    }
357
358
    /**
359
     * @param InputInterface  $input
360
     * @param OutputInterface $output
361
     *
362
     * @throws InvalidArgumentException
363
     */
364
    protected function createDatabase(InputInterface $input, OutputInterface $output)
365
    {
366
        $dbOptions = array('--dbHost', '--dbUser', '--dbPass', '--dbName');
367
        $dbOptionsFound = 0;
368
        foreach ($dbOptions as $dbOption) {
369
            foreach ($this->getCliArguments() as $definedCliOption) {
370
                if (BinaryString::startsWith($definedCliOption, $dbOption)) {
371
                    $dbOptionsFound++;
372
                }
373
            }
374
        }
375
376
        $hasAllOptions = $dbOptionsFound == 4;
377
378
        // if all database options were passed in at cmd line
379
        if ($hasAllOptions) {
380
            $this->config['db_host'] = $input->getOption('dbHost');
381
            $this->config['db_user'] = $input->getOption('dbUser');
382
            $this->config['db_pass'] = $input->getOption('dbPass');
383
            $this->config['db_name'] = $input->getOption('dbName');
384
            $this->config['db_port'] = $input->getOption('dbPort');
385
            $this->config['db_prefix'] = $input->getOption('dbPrefix');
386
            $db = $this->validateDatabaseSettings($output, $input);
387
388
            if ($db === false) {
389
                throw new InvalidArgumentException("Database configuration is invalid");
390
            }
391
392
        } else {
393
            $dialog = $this->getHelperSet()->get('dialog');
394
            do {
395
                $dbHostDefault = $input->getOption('dbHost') ? $input->getOption('dbHost') : 'localhost';
396
                $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...
397
398
                $dbUserDefault = $input->getOption('dbUser') ? $input->getOption('dbUser') : 'root';
399
                $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...
400
401
                $dbPassDefault = $input->getOption('dbPass') ? $input->getOption('dbPass') : '';
402
                $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...
403
404
                $dbNameDefault = $input->getOption('dbName') ? $input->getOption('dbName') : 'magento';
405
                $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...
406
407
                $dbPortDefault = $input->getOption('dbPort') ? $input->getOption('dbPort') : 3306;
408
                $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...
409
410
                $dbPrefixDefault = $input->getOption('dbPrefix') ? $input->getOption('dbPrefix') : '';
411
                $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...
412
                $db = $this->validateDatabaseSettings($output, $input);
413
            } while ($db === false);
414
        }
415
416
        $this->config['db'] = $db;
417
    }
418
419
    /**
420
     * @param OutputInterface $output
421
     * @param InputInterface  $input
422
     *
423
     * @return bool|PDO
424
     */
425
    protected function validateDatabaseSettings(OutputInterface $output, InputInterface $input)
426
    {
427
        try {
428
            $dsn = sprintf("mysql:host=%s;port=%s", $this->config['db_host'], $this->config['db_port']);
429
            $db = new PDO($dsn, $this->config['db_user'], $this->config['db_pass']);
430
            if (!$db->query('USE ' . $this->config['db_name'])) {
431
                $db->query("CREATE DATABASE `" . $this->config['db_name'] . "`");
432
                $output->writeln('<info>Created database ' . $this->config['db_name'] . '</info>');
433
                $db->query('USE ' . $this->config['db_name']);
434
435
                return $db;
436
            }
437
438
            if ($input->getOption('noDownload') && !$input->getOption('forceUseDb')) {
439
                $output->writeln("<error>Database {$this->config['db_name']} already exists.</error>");
440
441
                return false;
442
            }
443
444
            return $db;
445
        } catch (PDOException $e) {
446
            $output->writeln('<error>' . $e->getMessage() . '</error>');
447
        }
448
449
        return false;
450
    }
451
452
    /**
453
     * @param InputInterface $input
454
     * @param OutputInterface $output
455
     */
456
    protected function installSampleData(InputInterface $input, OutputInterface $output)
457
    {
458
        $magentoPackage = $this->config['magentoPackage']; /* @var $magentoPackage \Composer\Package\MemoryPackage */
459
        $extra = $magentoPackage->getExtra();
460
        if (!isset($extra['sample-data'])) {
461
            return;
462
        }
463
464
        $dialog = $this->getHelperSet()->get('dialog');
465
466
        $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...
467
468
        if ($installSampleData) {
469
            $filesystem = new Filesystem();
470
471
            foreach ($this->commandConfig['demo-data-packages'] as $demoPackageData) {
472
                if ($demoPackageData['name'] == $extra['sample-data']) {
473
                    $package = $this->downloadByComposerConfig(
474
                        $input,
475
                        $output,
476
                        $demoPackageData,
477
                        $this->config['installationFolder'] . '/_temp_demo_data',
478
                        false
479
                    );
480
481
                    $this->_fixComposerExtractionBug();
482
483
                    $expandedFolder = $this->config['installationFolder']
484
                                    . '/_temp_demo_data/'
485
                                    . 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...
486
                    if (is_dir($expandedFolder)) {
487
                        $filesystem->recursiveCopy(
488
                            $expandedFolder,
489
                            $this->config['installationFolder']
490
                        );
491
                        $filesystem->recursiveRemoveDirectory($expandedFolder);
492
                    }
493
494
                    // Remove empty folder
495 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...
496
                        $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/vendor/composer');
497
                    }
498
499
                    // Install sample data
500
                    $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...
501
                    $db = $this->config['db'];
502
                    /* @var $db PDO */
503
                    if (isset($sampleDataSqlFile[0])) {
504
                        if (OperatingSystem::isProgramInstalled('mysql')) {
505
                            $exec = 'mysql '
506
                                . '-h' . escapeshellarg(strval($this->config['db_host']))
507
                                . ' '
508
                                . '-u' . escapeshellarg(strval($this->config['db_user']))
509
                                . ' '
510
                                . ($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...
511
                                . (!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...
512
                                . strval($this->config['db_name'])
513
                                . ' < '
514
                                . escapeshellarg($sampleDataSqlFile[0]);
515
                            $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...
516
                            Exec::run($exec);
517
                            @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...
518
                        } else {
519
                            $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...
520
                            // Fallback -> Try to install dump file by PDO driver
521
                            $dbUtils = new DatabaseUtils();
522
                            $dbUtils->importSqlDump($db, $sampleDataSqlFile[0]);
523
                        }
524
                    }
525
                }
526
            }
527
528 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...
529
                $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/_temp_demo_data');
530
            }
531
        }
532
    }
533
534
    protected function _fixComposerExtractionBug()
535
    {
536
        $filesystem = new Filesystem();
537
        foreach (array('/_temp_demo_data/media' => '/media', '/_temp_demo_data/skin' => '/skin') as $wrong => $right) {
538
            $wrongFolder = $this->config['installationFolder'] . $wrong;
539
            $rightFolder = $this->config['installationFolder'] . $right;
540
            if (is_dir($wrongFolder)) {
541
                $filesystem->recursiveCopy(
542
                    $wrongFolder,
543
                    $rightFolder
544
                );
545
                $filesystem->recursiveRemoveDirectory($wrongFolder);
546
            }
547
        }
548
    }
549
550
    /**
551
     * Remove empty composer extraction folder
552
     */
553
    protected function removeEmptyFolders()
554
    {
555
        if (is_dir(getcwd() . '/vendor')) {
556
            $finder = new Finder();
557
            $finder->files()->depth(3)->in(getcwd() . '/vendor');
558
            if ($finder->count() == 0) {
559
                $filesystem = new Filesystem();
560
                $filesystem->recursiveRemoveDirectory(getcwd() . '/vendor');
561
            }
562
        }
563
    }
564
565
    /**
566
     * @param InputInterface  $input
567
     * @param OutputInterface $output
568
     *
569
     * @return array
570
     * @throws InvalidArgumentException parameter mismatch (e.g. base-url components like hostname)
571
     * @throws RuntimeException
572
     */
573
    protected function installMagento(InputInterface $input, OutputInterface $output)
574
    {
575
        $this->getApplication()->setAutoExit(false);
576
        $dialog = $this->getHelperSet()->get('dialog');
577
578
        $defaults = $this->commandConfig['installation']['defaults'];
579
580
        $useDefaultConfigParams = $this->_parseBoolOption($input->getOption('useDefaultConfigParams'));
581
582
        $sessionSave = $useDefaultConfigParams ? $defaults['session_save'] : $dialog->ask(
583
            $output,
584
            '<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...
585
            $defaults['session_save']
586
        );
587
588
        $adminFrontname = $useDefaultConfigParams ? $defaults['admin_frontname'] : $dialog->askAndValidate(
589
            $output,
590
            '<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...
591
            $this->notEmptyCallback,
592
            false,
593
            $defaults['admin_frontname']
594
        );
595
596
        $currency = $useDefaultConfigParams ? $defaults['currency'] : $dialog->askAndValidate(
597
            $output,
598
            '<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...
599
            $this->notEmptyCallback,
600
            false,
601
            $defaults['currency']
602
        );
603
604
        $locale = $useDefaultConfigParams ? $defaults['locale'] : $dialog->askAndValidate(
605
            $output,
606
            '<question>Please enter the locale code:</question> <comment>[' . $defaults['locale'] . ']</comment>: ',
607
            $this->notEmptyCallback,
608
            false,
609
            $defaults['locale']
610
        );
611
612
        $timezone = $useDefaultConfigParams ? $defaults['timezone'] : $dialog->askAndValidate(
613
            $output,
614
            '<question>Please enter the timezone:</question> <comment>[' . $defaults['timezone'] . ']</comment>: ',
615
            $this->notEmptyCallback,
616
            false,
617
            $defaults['timezone']
618
        );
619
620
        $adminUsername = $useDefaultConfigParams ? $defaults['admin_username'] : $dialog->askAndValidate(
621
            $output,
622
            '<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...
623
            $this->notEmptyCallback,
624
            false,
625
            $defaults['admin_username']
626
        );
627
628
        $adminPassword = $useDefaultConfigParams ? $defaults['admin_password'] : $dialog->askAndValidate(
629
            $output,
630
            '<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...
631
            $this->notEmptyCallback,
632
            false,
633
            $defaults['admin_password']
634
        );
635
636
        $adminFirstname = $useDefaultConfigParams ? $defaults['admin_firstname'] : $dialog->askAndValidate(
637
            $output,
638
            '<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...
639
            $this->notEmptyCallback,
640
            false,
641
            $defaults['admin_firstname']
642
        );
643
644
        $adminLastname = $useDefaultConfigParams ? $defaults['admin_lastname'] : $dialog->askAndValidate(
645
            $output,
646
            '<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...
647
            $this->notEmptyCallback,
648
            false,
649
            $defaults['admin_lastname']
650
        );
651
652
        $adminEmail = $useDefaultConfigParams ? $defaults['admin_email'] : $dialog->askAndValidate(
653
            $output,
654
            '<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...
655
            $this->notEmptyCallback,
656
            false,
657
            $defaults['admin_email']
658
        );
659
660
        $validateBaseUrl = function($input) {
661
            if (!preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $input)) {
662
                throw new InvalidArgumentException('Please enter a valid URL');
663
            }
664
            if (parse_url($input, \PHP_URL_HOST) == 'localhost') {
665
                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...
666
            }
667
            return $input;
668
        };
669
670
        $baseUrl = ($input->getOption('baseUrl') !== null) ? $input->getOption('baseUrl') : $dialog->askAndValidate(
671
            $output,
672
            '<question>Please enter the base url:</question> ',
673
            $validateBaseUrl,
674
            false
675
        );
676
        $baseUrl = rtrim($baseUrl, '/') . '/'; // normalize baseUrl
677
678
        /**
679
         * Correct session save (common mistake)
680
         */
681
        if ($sessionSave == 'file') {
682
            $sessionSave = 'files';
683
        }
684
685
        /**
686
         * Try to create session folder
687
         */
688
        $defaultSessionFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/session';
689
        if ($sessionSave == 'files' && !is_dir($defaultSessionFolder)) {
690
            @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...
691
        }
692
693
        $dbHost = $this->config['db_host'];
694
        if ($this->config['db_port'] != 3306) {
695
            $dbHost .= ':' . $this->config['db_port'];
696
        }
697
698
        $argv = array(
699
            'license_agreement_accepted' => 'yes',
700
            'locale'                     => $locale,
701
            'timezone'                   => $timezone,
702
            'db_host'                    => $dbHost,
703
            'db_name'                    => $this->config['db_name'],
704
            'db_user'                    => $this->config['db_user'],
705
            'db_pass'                    => $this->config['db_pass'],
706
            'db_prefix'                  => $this->config['db_prefix'],
707
            'url'                        => $baseUrl,
708
            'use_rewrites'               => 'yes',
709
            'use_secure'                 => 'no',
710
            'secure_base_url'            => '',
711
            'use_secure_admin'           => 'no',
712
            'admin_username'             => $adminUsername,
713
            'admin_lastname'             => $adminLastname,
714
            'admin_firstname'            => $adminFirstname,
715
            'admin_email'                => $adminEmail,
716
            'admin_password'             => $adminPassword,
717
            'session_save'               => $sessionSave,
718
            'admin_frontname'            => $adminFrontname, /* magento 1 */
719
            'backend_frontname'          => $adminFrontname, /* magento 2 */
720
            'default_currency'           => $currency,
721
            'skip_url_validation'        => 'yes',
722
        );
723
        if ($useDefaultConfigParams) {
724
            if (strlen($defaults['encryption_key']) > 0) {
725
                $argv['encryption_key'] = $defaults['encryption_key'];
726
            }
727
            if (strlen($defaults['use_secure']) > 0) {
728
                $argv['use_secure'] = $defaults['use_secure'];
729
                $argv['secure_base_url'] = str_replace('http://', 'https://', $baseUrl);
730
            }
731
            if (strlen($defaults['use_rewrites']) > 0) {
732
                $argv['use_rewrites'] = $defaults['use_rewrites'];
733
            }
734
        }
735
        $installArgs = '';
736
        foreach ($argv as $argName => $argValue) {
737
            $installArgs .= '--' . $argName . ' ' . escapeshellarg($argValue) . ' ';
738
        }
739
740
        $output->writeln('<info>Start installation process.</info>');
741
742
        if (OperatingSystem::isWindows()) {
743
            $installCommand = 'php -f ' . escapeshellarg($this->getInstallScriptPath()) . ' -- ' . $installArgs;
744
        } else {
745
            $installCommand = '/usr/bin/env php -f ' . escapeshellarg($this->getInstallScriptPath()) . ' -- ' . $installArgs;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 125 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...
746
        }
747
        $output->writeln('<comment>' . $installCommand . '</comment>');
748
        Exec::run($installCommand, $installationOutput, $returnStatus);
749
        if ($returnStatus !== self::EXEC_STATUS_OK) {
750
            $this->getApplication()->setAutoExit(true);
751
            throw new RuntimeException('Installation failed.' . $installationOutput, 1);
752
        } else {
753
            $output->writeln('<info>Successfully installed Magento</info>');
754
            $encryptionKey = trim(substr($installationOutput, strpos($installationOutput, ':') + 1));
755
            $output->writeln('<comment>Encryption Key:</comment> <info>' . $encryptionKey . '</info>');
756
        }
757
758
        $dialog = $this->getHelperSet()->get('dialog');
759
760
        /**
761
         * Htaccess file
762
         */
763
        if ($input->getOption('useDefaultConfigParams') == null || $input->getOption('replaceHtaccessFile') != null) {
764
            $replaceHtaccessFile = false;
765
766
            if ($this->_parseBoolOption($input->getOption('replaceHtaccessFile'))) {
767
                $replaceHtaccessFile = true;
768
            } elseif ($dialog->askConfirmation(
769
                $output,
770
                '<question>Write BaseURL to .htaccess file?</question> <comment>[n]</comment>: ',
771
                false)
772
            ) {
773
                $replaceHtaccessFile = true;
774
            }
775
776
            if ($replaceHtaccessFile) {
777
                $this->replaceHtaccessFile($baseUrl);
778
            }
779
        }
780
781
        \chdir($this->config['installationFolder']);
782
        $this->getApplication()->reinit();
783
        $output->writeln('<info>Reindex all after installation</info>');
784
        $this->getApplication()->run(new StringInput('index:reindex:all'), $output);
785
        $this->getApplication()->run(new StringInput('sys:check'), $output);
786
        $output->writeln('<info>Successfully installed magento</info>');
787
    }
788
789
    /**
790
     * Check if we have a magento 2 or 1 installation and return path to install.php
791
     *
792
     * @return string
793
     */
794
    protected function getInstallScriptPath()
795
    {
796
        $magento1InstallScriptPath  = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'install.php';
797
        $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...
798
        if (file_exists($magento2InstallScriptPath)) {
799
            return $magento2InstallScriptPath;
800
        }
801
802
        return $magento1InstallScriptPath;
803
    }
804
805
    /**
806
     * @param string $baseUrl
807
     */
808
    protected function replaceHtaccessFile($baseUrl)
809
    {
810
        $content = file_get_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess');
811
        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...
812
        $content = str_replace('#RewriteBase /magento/', 'RewriteBase ' . parse_url($baseUrl, PHP_URL_PATH), $content);
813
        file_put_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess', $content);
814
    }
815
816
    /**
817
     * @param OutputInterface $output
818
     */
819
    protected function setDirectoryPermissions($output)
820
    {
821
        try {
822
            $varFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var';
823
            if (!is_dir($varFolder)) {
824
                @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...
825
            }
826
            @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...
827
828
            $varCacheFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/cache';
829
            if (!is_dir($varCacheFolder)) {
830
                @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...
831
            }
832
            @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...
833
834
            $mediaFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'media';
835
            if (!is_dir($mediaFolder)) {
836
                @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...
837
            }
838
            @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...
839
840
            $finder = Finder::create();
841
            $finder->directories()
842
                ->ignoreUnreadableDirs(true)
843
                ->in(array($varFolder, $mediaFolder));
844
            foreach ($finder as $dir) {
845
                @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...
846
            }
847
        } catch (Exception $e) {
848
            $output->writeln('<error>' . $e->getMessage() . '</error>');
849
        }
850
    }
851
852
    /**
853
     * @return array
854
     */
855
    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...
856
    {
857
        if ($this->_argv === null) {
858
            $this->_argv = $_SERVER['argv'];
859
        }
860
861
        return $this->_argv;
862
    }
863
864
    /**
865
     * @param array $args
866
     */
867
    public function setCliArguments($args)
868
    {
869
        $this->_argv = $args;
870
    }
871
}
872