InstallCommand::setCliArguments()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace N98\Magento\Command\Installer;
4
5
use Composer\Composer;
6
use Composer\Package\CompletePackage;
7
use Exception;
8
use InvalidArgumentException;
9
use N98\Magento\Command\AbstractMagentoCommand;
10
use N98\Util\BinaryString;
11
use N98\Util\Database as DatabaseUtils;
12
use N98\Util\Exec;
13
use N98\Util\Filesystem;
14
use N98\Util\OperatingSystem;
15
use N98\Util\VerifyOrDie;
16
use PDO;
17
use PDOException;
18
use RuntimeException;
19
use Symfony\Component\Console\Helper\DialogHelper;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Input\StringInput;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Finder\Finder;
25
26
/**
27
 * Class InstallCommand
28
 *
29
 * @codeCoverageIgnore  - Travis server uses installer to create a new shop. If it not works complete build fails.
30
 * @package N98\Magento\Command\Installer
31
 */
32
class InstallCommand extends AbstractMagentoCommand
33
{
34
    /**
35
     * @deprecated since since 1.97.22; Use constant from Exec-Utility instead
36
     * @see Exec::CODE_CLEAN_EXIT
37
     */
38
    const EXEC_STATUS_OK = 0;
39
40
    const DEFAULT_SESSION_PATH = 'var/session';
41
42
    const MAGENTO_INSTALL_SCRIPT_PATH = 'install.php';
43
44
    /**
45
     * @var array
46
     */
47
    protected $config;
48
49
    /**
50
     * @var array
51
     */
52
    protected $_argv;
53
54
    /**
55
     * @var array
56
     */
57
    protected $commandConfig;
58
59
    /**
60
     * @var \Closure
61
     */
62
    protected $notEmptyCallback;
63
64
    protected function configure()
65
    {
66
        $this
67
            ->setName('install')
68
            ->addOption('magentoVersion', null, InputOption::VALUE_OPTIONAL, 'Magento version')
69
            ->addOption(
70
                'magentoVersionByName',
71
                null,
72
                InputOption::VALUE_OPTIONAL,
73
                'Magento version name instead of order number'
74
            )
75
            ->addOption('installationFolder', null, InputOption::VALUE_OPTIONAL, 'Installation folder')
76
            ->addOption('dbHost', null, InputOption::VALUE_OPTIONAL, 'Database host')
77
            ->addOption('dbUser', null, InputOption::VALUE_OPTIONAL, 'Database user')
78
            ->addOption('dbPass', null, InputOption::VALUE_OPTIONAL, 'Database password')
79
            ->addOption('dbName', null, InputOption::VALUE_OPTIONAL, 'Database name')
80
            ->addOption('dbPort', null, InputOption::VALUE_OPTIONAL, 'Database port', 3306)
81
            ->addOption('dbPrefix', null, InputOption::VALUE_OPTIONAL, 'Table prefix', '')
82
            ->addOption('installSampleData', null, InputOption::VALUE_OPTIONAL, 'Install sample data')
83
            ->addOption(
84
                'useDefaultConfigParams',
85
                null,
86
                InputOption::VALUE_OPTIONAL,
87
                'Use default installation parameters defined in the yaml file'
88
            )->addOption('baseUrl', null, InputOption::VALUE_OPTIONAL, 'Installation base url')
89
            ->addOption(
90
                'replaceHtaccessFile',
91
                null,
92
                InputOption::VALUE_OPTIONAL,
93
                'Generate htaccess file (for non vhost environment)'
94
            )->addOption(
95
                'noDownload',
96
                null,
97
                InputOption::VALUE_NONE,
98
                'If set skips download step. Used when installationFolder is already a Magento installation that has ' .
99
                'to be installed on the given database.'
100
            )
101
            ->addOption(
102
                'only-download',
103
                null,
104
                InputOption::VALUE_NONE,
105
                'Downloads (and extracts) source code'
106
            )
107
            ->addOption(
108
                'forceUseDb',
109
                null,
110
                InputOption::VALUE_NONE,
111
                'If --noDownload passed, force to use given database if it already exists.'
112
            )->setDescription('Install magento');
113
114
        $help = <<<HELP
115
* Download Magento by a list of git repos and zip files (mageplus,
116
  magelte, official community packages).
117
* Try to create database if it does not exist.
118
* Installs Magento sample data if available (since version 1.2.0).
119
* Starts Magento installer
120
* Sets rewrite base in .htaccess file
121
122
Example of an unattended Magento CE/ Open Source 1.9.3.8 installation:
123
124
   $ n98-magerun.phar install --dbHost="localhost" --dbUser="mydbuser" \
125
     --dbPass="mysecret" --dbName="magentodb" --installSampleData=yes \
126
     --useDefaultConfigParams=yes \
127
     --magentoVersionByName="magento-mirror-1.9.3.8" \
128
     --installationFolder="magento" --baseUrl="http://magento.localdomain/"
129
130
(Magento is only freely available via Github with Magerun, it uses the best
131
community mirror)
132
133
Additionally, with --noDownload option you can install Magento working
134
copy already stored in --installationFolder on the given database.
135
136
See it in action: http://youtu.be/WU-CbJ86eQc
137
138
HELP;
139
        $this->setHelp($help);
140
141
        $this->notEmptyCallback = function ($input) {
142
            if (empty($input)) {
143
                throw new InvalidArgumentException('Please enter a value');
144
            }
145
146
            return $input;
147
        };
148
    }
149
150
    /**
151
     * @return bool
152
     */
153
    public function isEnabled()
154
    {
155
        return Exec::allowed();
156
    }
157
158
    /**
159
     * @param InputInterface $input
160
     * @param OutputInterface $output
161
     * @throws RuntimeException
162
     * @return int|null|void
163
     */
164
    protected function execute(InputInterface $input, OutputInterface $output)
165
    {
166
        $this->commandConfig = $this->getCommandConfig();
167
        $this->writeSection($output, 'Magento Installation');
168
169
        $this->precheckPhp();
170
171
        if (!$input->getOption('noDownload')) {
172
            $this->selectMagentoVersion($input, $output);
173
        }
174
175
        $this->chooseInstallationFolder($input, $output);
176
177
        if (!$input->getOption('noDownload')) {
178
            $result = $this->downloadMagento($input, $output);
179
180
            if ($result === false) {
181
                return 1;
182
            }
183
        }
184
185
        if ($input->getOption('only-download')) {
186
            return 0;
187
        }
188
189
        $this->createDatabase($input, $output);
190
191
        if (!$input->getOption('noDownload')) {
192
            $this->installSampleData($input, $output);
193
        }
194
195
        $this->removeEmptyFolders();
196
        $this->setDirectoryPermissions($output);
197
        $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...
198
    }
199
200
    /**
201
     * Check PHP environment agains minimal required settings modules
202
     */
203
    protected function precheckPhp()
204
    {
205
        $extensions = $this->commandConfig['installation']['pre-check']['php']['extensions'];
206
        $missingExtensions = array();
207
        foreach ($extensions as $extension) {
208
            if (!extension_loaded($extension)) {
209
                $missingExtensions[] = $extension;
210
            }
211
        }
212
213
        if (count($missingExtensions) > 0) {
214
            throw new RuntimeException(
215
                'The following PHP extensions are required to start installation: ' . implode(',', $missingExtensions)
216
            );
217
        }
218
    }
219
220
    /**
221
     * @param InputInterface $input
222
     * @param OutputInterface $output
223
     *
224
     * @throws InvalidArgumentException
225
     */
226
    protected function selectMagentoVersion(InputInterface $input, OutputInterface $output)
227
    {
228
        if ($input->getOption('magentoVersion') == null && $input->getOption('magentoVersionByName') == null) {
229
            $question = array();
230
            foreach ($this->commandConfig['magento-packages'] as $key => $package) {
231
                $question[] = '<comment>' . str_pad('[' . ($key + 1) . ']', 4, ' ') . '</comment> ' .
232
                    $package['name'] . "\n";
233
            }
234
            $question[] = "<question>Choose a magento version:</question> ";
235
236
            $commandConfig = $this->commandConfig;
237
238
            $type = $this->getHelper('dialog')->askAndValidate(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method askAndValidate() does only exist in the following implementations of said interface: N98\Util\Console\Helper\ParameterHelper, Symfony\Component\Console\Helper\DialogHelper.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
239
                $output,
240
                $question,
241
                function ($typeInput) use ($commandConfig) {
242
                    if (!in_array($typeInput, range(1, count($commandConfig['magento-packages'])))) {
243
                        throw new InvalidArgumentException('Invalid type');
244
                    }
245
246
                    return $typeInput;
247
                }
248
            );
249
        } else {
250
            $type = null;
251
252
            if ($input->getOption('magentoVersion')) {
253
                $type = $input->getOption('magentoVersion');
254
                if ($type !== (string) (int) $type) {
255
                    $type = $this->getPackageNumberByName($type);
256
                }
257
            } elseif ($input->getOption('magentoVersionByName')) {
258
                $type = $this->getPackageNumberByName($input->getOption('magentoVersionByName'));
259
            }
260
261
            if ($type == null) {
262
                throw new InvalidArgumentException('Unable to locate Magento version');
263
            }
264
        }
265
266
        $magentoPackages = $this->commandConfig['magento-packages'];
267
268
        $index = $type - 1;
269
        if (!isset($magentoPackages[$index])) {
270
            throw new InvalidArgumentException(
271
                sprintf(
272
                    'Invalid Magento package number %s, must be from 1 to %d.',
273
                    var_export($type, true),
274
                    count($magentoPackages)
275
                )
276
            );
277
        }
278
279
        $this->config['magentoVersionData'] = $magentoPackages[$index];
280
    }
281
282
    /**
283
     * @param $name
284
     *
285
     * @return int 1 or greater as the one-based package number, null on failure to resolve the name
286
     */
287
    private function getPackageNumberByName($name)
288
    {
289
        // directly filter integer strings
290
        if ($name === (string) (int) $name) {
291
            return (int) $name;
292
        }
293
294
        $magentoPackages = $this->commandConfig['magento-packages'];
295
296
        foreach ($magentoPackages as $key => $package) {
297
            if ($package['name'] === $name) {
298
                return $key + 1;
299
            }
300
        }
301
302
        return;
303
    }
304
305
    /**
306
     * @param InputInterface $input
307
     * @param OutputInterface $output
308
     * @return bool
309
     */
310
    public function downloadMagento(InputInterface $input, OutputInterface $output)
311
    {
312
        try {
313
            $package = $this->createComposerPackageByConfig($this->config['magentoVersionData']);
314
            $this->config['magentoPackage'] = $package;
315
316
            $installationFolder = $this->config['installationFolder'];
317
318
            if (file_exists($installationFolder . '/app/Mage.php')) {
319
                $output->writeln(
320
                    sprintf(
321
                        '<error>A magento installation already exists in this folder "%s"</error>',
322
                        $installationFolder
323
                    )
324
                );
325
326
                return false;
327
            }
328
329
            $composer = $this->getComposer($input, $output);
330
            $targetFolder = $this->getTargetFolderByType($composer, $package, $installationFolder);
331
            $this->config['magentoPackage'] = $this->downloadByComposerConfig(
332
                $input,
333
                $output,
334
                $package,
335
                $targetFolder,
336
                true
337
            );
338
339
            if ($this->isSourceTypeRepository($package->getSourceType())) {
340
                $filesystem = new \N98\Util\Filesystem;
341
                $filesystem->recursiveCopy($targetFolder, $installationFolder, array('.git', '.hg'));
342
            } else {
343
                $filesystem = new \Composer\Util\Filesystem();
344
                $filesystem->copyThenRemove(
345
                    $installationFolder . '/_n98_magerun_download',
346
                    $installationFolder
347
                );
348
            }
349
350
            if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
351
                // Patch installer
352
                $this->patchMagentoInstallerForPHP54($installationFolder);
353
            }
354
        } catch (Exception $e) {
355
            $output->writeln('<error>' . $e->getMessage() . '</error>');
356
357
            return false;
358
        }
359
360
        return true;
361
    }
362
363
    /**
364
     * construct a folder to where magerun will download the source to, cache git/hg repositories under COMPOSER_HOME
365
     *
366
     * @param Composer $composer
367
     * @param CompletePackage $package
368
     * @param $installationFolder
369
     *
370
     * @return string
371
     */
372
    protected function getTargetFolderByType(Composer $composer, CompletePackage $package, $installationFolder)
373
    {
374
        $type = $package->getSourceType();
375
        if ($this->isSourceTypeRepository($type)) {
376
            $targetPath = sprintf(
377
                '%s/%s/%s/%s',
378
                $composer->getConfig()->get('cache-dir'),
379
                '_n98_magerun_download',
380
                $type,
381
                preg_replace('{[^a-z0-9.]}i', '-', $package->getSourceUrl())
382
            );
383
        } else {
384
            $targetPath = sprintf(
385
                '%s/%s',
386
                $installationFolder,
387
                '_n98_magerun_download'
388
            );
389
        }
390
391
        return $targetPath;
392
    }
393
394
    /**
395
     * @param string $magentoFolder
396
     */
397
    protected function patchMagentoInstallerForPHP54($magentoFolder)
398
    {
399
        $installerConfig = $magentoFolder
400
            . DIRECTORY_SEPARATOR
401
            . 'app/code/core/Mage/Install/etc/config.xml';
402
        if (file_exists($installerConfig)) {
403
            $xml = file_get_contents($installerConfig);
404
            file_put_contents($installerConfig, str_replace('<pdo_mysql/>', '<pdo_mysql>1</pdo_mysql>', $xml));
405
        }
406
    }
407
408
    /**
409
     * @param InputInterface $input
410
     * @param OutputInterface $output
411
     *
412
     * @throws InvalidArgumentException
413
     */
414
    protected function createDatabase(InputInterface $input, OutputInterface $output)
415
    {
416
        $dbOptions = array('--dbHost', '--dbUser', '--dbPass', '--dbName');
417
        $dbOptionsFound = 0;
418
        foreach ($dbOptions as $dbOption) {
419
            foreach ($this->getCliArguments() as $definedCliOption) {
420
                if (BinaryString::startsWith($definedCliOption, $dbOption)) {
421
                    $dbOptionsFound++;
422
                }
423
            }
424
        }
425
426
        $hasAllOptions = $dbOptionsFound == 4;
427
428
        // if all database options were passed in at cmd line
429
        if ($hasAllOptions) {
430
            $this->config['db_host'] = $input->getOption('dbHost');
431
            $this->config['db_user'] = $input->getOption('dbUser');
432
            $this->config['db_pass'] = $input->getOption('dbPass');
433
            $this->config['db_name'] = VerifyOrDie::filename(
434
                $input->getOption('dbName'),
435
                'Database name is not portable'
436
            );
437
            $this->config['db_port'] = $input->getOption('dbPort');
438
            $this->config['db_prefix'] = $input->getOption('dbPrefix');
439
            $db = $this->validateDatabaseSettings($output, $input);
440
441
            if ($db === false) {
442
                throw new InvalidArgumentException("Database configuration is invalid");
443
            }
444
        } else {
445
            /** @var DialogHelper $dialog */
446
            $dialog = $this->getHelper('dialog');
447
            do {
448
                $dbHostDefault = $input->getOption('dbHost') ? $input->getOption('dbHost') : 'localhost';
449
                $this->config['db_host'] = $dialog->askAndValidate(
450
                    $output,
451
                    '<question>Please enter the database host</question> <comment>[' . $dbHostDefault . ']</comment>: ',
452
                    $this->notEmptyCallback,
453
                    false,
454
                    $dbHostDefault
455
                );
456
457
                $dbUserDefault = $input->getOption('dbUser') ? $input->getOption('dbUser') : 'root';
458
                $this->config['db_user'] = $dialog->askAndValidate(
459
                    $output,
460
                    '<question>Please enter the database username</question> <comment>[' . $dbUserDefault .
461
                    ']</comment>: ',
462
                    $this->notEmptyCallback,
463
                    false,
464
                    $dbUserDefault
465
                );
466
467
                $dbPassDefault = $input->getOption('dbPass') ? $input->getOption('dbPass') : '';
468
                $this->config['db_pass'] = $dialog->ask(
469
                    $output,
470
                    '<question>Please enter the database password</question> <comment>[' . $dbPassDefault .
471
                    ']</comment>: ',
472
                    $dbPassDefault
473
                );
474
475
                $dbNameDefault = $input->getOption('dbName') ? $input->getOption('dbName') : 'magento';
476
                $this->config['db_name'] = $dialog->askAndValidate(
477
                    $output,
478
                    '<question>Please enter the database name</question> <comment>[' . $dbNameDefault . ']</comment>: ',
479
                    $this->notEmptyCallback,
480
                    false,
481
                    $dbNameDefault
482
                );
483
484
                $dbPortDefault = $input->getOption('dbPort') ? $input->getOption('dbPort') : 3306;
485
                $this->config['db_port'] = $dialog->askAndValidate(
486
                    $output,
487
                    '<question>Please enter the database port </question> <comment>[' . $dbPortDefault .
488
                    ']</comment>: ',
489
                    $this->notEmptyCallback,
490
                    false,
491
                    $dbPortDefault
492
                );
493
494
                $dbPrefixDefault = $input->getOption('dbPrefix') ? $input->getOption('dbPrefix') : '';
495
                $this->config['db_prefix'] = $dialog->ask(
496
                    $output,
497
                    '<question>Please enter the table prefix</question> <comment>[' . $dbPrefixDefault . ']</comment>:',
498
                    $dbPrefixDefault
499
                );
500
                $db = $this->validateDatabaseSettings($output, $input);
501
            } while ($db === false);
502
        }
503
504
        $this->config['db'] = $db;
505
    }
506
507
    /**
508
     * @param OutputInterface $output
509
     * @param InputInterface $input
510
     *
511
     * @return bool|PDO
512
     */
513
    protected function validateDatabaseSettings(OutputInterface $output, InputInterface $input)
514
    {
515
        try {
516
            $dsn = sprintf("mysql:host=%s;port=%s", $this->config['db_host'], $this->config['db_port']);
517
            $db = new PDO($dsn, $this->config['db_user'], $this->config['db_pass']);
518
            if (!$db->query('USE ' . $this->config['db_name'])) {
519
                $db->query("CREATE DATABASE `" . $this->config['db_name'] . "`");
520
                $output->writeln('<info>Created database ' . $this->config['db_name'] . '</info>');
521
                $db->query('USE ' . $this->config['db_name']);
522
523
                return $db;
524
            }
525
        } catch (PDOException $e) {
526
            $output->writeln('<error>' . $e->getMessage() . '</error>');
527
            return false;
528
        }
529
530
        if ($input->getOption('noDownload') && !$input->getOption('forceUseDb')) {
531
            $output->writeln(
532
                sprintf(
533
                    "<error>Database '%s' already exists, use --forceUseDb in combination with --noDownload" .
534
                    " to use an existing database</error>",
535
                    $this->config['db_name']
536
                )
537
            );
538
            return false;
539
        }
540
541
        return $db;
542
    }
543
544
    /**
545
     * @param InputInterface $input
546
     * @param OutputInterface $output
547
     */
548
    protected function installSampleData(InputInterface $input, OutputInterface $output)
549
    {
550
        $magentoPackage = $this->config['magentoPackage'];
551
        /* @var $magentoPackage \Composer\Package\MemoryPackage */
552
        $extra = $magentoPackage->getExtra();
553
        if (!isset($extra['sample-data'])) {
554
            return;
555
        }
556
557
        $dialog = $this->getHelper('dialog');
558
559
        $installSampleData = ($input->getOption('installSampleData') !== null)
560
            ? $this->_parseBoolOption($input->getOption('installSampleData'))
561
            : $dialog->askConfirmation(
562
                $output,
563
                '<question>Install sample data?</question> <comment>[y]</comment>: '
564
            );
565
566
        if ($installSampleData) {
567
            $filesystem = new Filesystem();
568
569
            foreach ($this->commandConfig['demo-data-packages'] as $demoPackageData) {
570
                if ($demoPackageData['name'] == $extra['sample-data']) {
571
                    $package = $this->downloadByComposerConfig(
572
                        $input,
573
                        $output,
574
                        $demoPackageData,
575
                        $this->config['installationFolder'] . '/_temp_demo_data',
576
                        false
577
                    );
578
579
                    $this->_fixComposerExtractionBug();
580
581
                    $expandedFolder = $this->config['installationFolder']
582
                        . '/_temp_demo_data/'
583
                        . str_replace(array('.tar.gz', '.tar.bz2', '.zip'), '', basename($package->getDistUrl()));
584
                    if (is_dir($expandedFolder)) {
585
                        $filesystem->recursiveCopy(
586
                            $expandedFolder,
587
                            $this->config['installationFolder']
588
                        );
589
                        $filesystem->recursiveRemoveDirectory($expandedFolder);
590
                    }
591
592
                    // Remove empty folder
593 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...
594
                        $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/vendor/composer');
595
                    }
596
597
                    // Install sample data
598
                    $sampleDataSqlFile = glob(
599
                        $this->config['installationFolder'] . '/_temp_demo_data/magento_*sample_data*sql'
600
                    );
601
                    $db = $this->config['db'];
602
                    /* @var $db PDO */
603
                    if (isset($sampleDataSqlFile[0])) {
604
                        if (OperatingSystem::isProgramInstalled('mysql')) {
605
                            $exec = 'mysql '
606
                                . '-h' . escapeshellarg(strval($this->config['db_host']))
607
                                . ' '
608
                                . '-u' . escapeshellarg(strval($this->config['db_user']))
609
                                . ' '
610
                                . ($this->config['db_port'] != '3306'
611
                                    ? '-P' . escapeshellarg($this->config['db_port']) . ' ' : '')
612
                                . (!strval($this->config['db_pass'] == '')
613
                                    ? '-p' . escapeshellarg($this->config['db_pass']) . ' ' : '')
614
                                . strval($this->config['db_name'])
615
                                . ' < '
616
                                . escapeshellarg($sampleDataSqlFile[0]);
617
                            $output->writeln(
618
                                '<info>Importing <comment>' . $sampleDataSqlFile[0] .
619
                                '</comment> with mysql cli client</info>'
620
                            );
621
                            Exec::run($exec);
622
                            @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...
623
                        } else {
624
                            $output->writeln(
625
                                '<info>Importing <comment>' . $sampleDataSqlFile[0] .
626
                                '</comment> with PDO driver</info>'
627
                            );
628
                            // Fallback -> Try to install dump file by PDO driver
629
                            $dbUtils = new DatabaseUtils();
630
                            $dbUtils->importSqlDump($db, $sampleDataSqlFile[0]);
631
                        }
632
                    }
633
                }
634
            }
635
636 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...
637
                $filesystem->recursiveRemoveDirectory($this->config['installationFolder'] . '/_temp_demo_data');
638
            }
639
        }
640
    }
641
642
    protected function _fixComposerExtractionBug()
643
    {
644
        $filesystem = new Filesystem();
645
        foreach (array('/_temp_demo_data/media' => '/media', '/_temp_demo_data/skin' => '/skin') as $wrong => $right) {
646
            $wrongFolder = $this->config['installationFolder'] . $wrong;
647
            $rightFolder = $this->config['installationFolder'] . $right;
648
            if (is_dir($wrongFolder)) {
649
                $filesystem->recursiveCopy(
650
                    $wrongFolder,
651
                    $rightFolder
652
                );
653
                $filesystem->recursiveRemoveDirectory($wrongFolder);
654
            }
655
        }
656
    }
657
658
    /**
659
     * Remove empty composer extraction folder
660
     */
661
    protected function removeEmptyFolders()
662
    {
663
        if (is_dir(getcwd() . '/vendor')) {
664
            $finder = new Finder();
665
            $finder->files()->depth(3)->in(getcwd() . '/vendor');
666
            if ($finder->count() == 0) {
667
                $filesystem = new Filesystem();
668
                $filesystem->recursiveRemoveDirectory(getcwd() . '/vendor');
669
            }
670
        }
671
    }
672
673
    /**
674
     * @param InputInterface $input
675
     * @param OutputInterface $output
676
     *
677
     * @return array
678
     * @throws InvalidArgumentException parameter mismatch (e.g. base-url components like hostname)
679
     * @throws RuntimeException
680
     */
681
    protected function installMagento(InputInterface $input, OutputInterface $output)
682
    {
683
        $this->getApplication()->setAutoExit(false);
684
        /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */
685
        $dialog = $this->getHelper('dialog');
686
687
        $defaults = $this->commandConfig['installation']['defaults'];
688
689
        $useDefaultConfigParams = $this->_parseBoolOption($input->getOption('useDefaultConfigParams'));
690
691
        $sessionSave = $useDefaultConfigParams ? $defaults['session_save'] : $dialog->ask(
692
            $output,
693
            '<question>Please enter the session save:</question> <comment>[' .
694
            $defaults['session_save'] . ']</comment>: ',
695
            $defaults['session_save']
696
        );
697
698
        $adminFrontname = $useDefaultConfigParams ? $defaults['admin_frontname'] : $dialog->askAndValidate(
699
            $output,
700
            '<question>Please enter the admin frontname:</question> <comment>[' .
701
            $defaults['admin_frontname'] . ']</comment> ',
702
            $this->notEmptyCallback,
703
            false,
704
            $defaults['admin_frontname']
705
        );
706
707
        $currency = $useDefaultConfigParams ? $defaults['currency'] : $dialog->askAndValidate(
708
            $output,
709
            '<question>Please enter the default currency code:</question> <comment>[' .
710
            $defaults['currency'] . ']</comment>: ',
711
            $this->notEmptyCallback,
712
            false,
713
            $defaults['currency']
714
        );
715
716
        $locale = $useDefaultConfigParams ? $defaults['locale'] : $dialog->askAndValidate(
717
            $output,
718
            '<question>Please enter the locale code:</question> <comment>[' . $defaults['locale'] . ']</comment>: ',
719
            $this->notEmptyCallback,
720
            false,
721
            $defaults['locale']
722
        );
723
724
        $timezone = $useDefaultConfigParams ? $defaults['timezone'] : $dialog->askAndValidate(
725
            $output,
726
            '<question>Please enter the timezone:</question> <comment>[' . $defaults['timezone'] . ']</comment>: ',
727
            $this->notEmptyCallback,
728
            false,
729
            $defaults['timezone']
730
        );
731
732
        $adminUsername = $useDefaultConfigParams ? $defaults['admin_username'] : $dialog->askAndValidate(
733
            $output,
734
            '<question>Please enter the admin username:</question> <comment>[' .
735
            $defaults['admin_username'] . ']</comment>: ',
736
            $this->notEmptyCallback,
737
            false,
738
            $defaults['admin_username']
739
        );
740
741
        $adminPassword = $useDefaultConfigParams ? $defaults['admin_password'] : $dialog->askAndValidate(
742
            $output,
743
            '<question>Please enter the admin password:</question> <comment>[' .
744
            $defaults['admin_password'] . ']</comment>: ',
745
            $this->notEmptyCallback,
746
            false,
747
            $defaults['admin_password']
748
        );
749
750
        $adminFirstname = $useDefaultConfigParams ? $defaults['admin_firstname'] : $dialog->askAndValidate(
751
            $output,
752
            '<question>Please enter the admin\'s firstname:</question> <comment>[' .
753
            $defaults['admin_firstname'] . ']</comment>: ',
754
            $this->notEmptyCallback,
755
            false,
756
            $defaults['admin_firstname']
757
        );
758
759
        $adminLastname = $useDefaultConfigParams ? $defaults['admin_lastname'] : $dialog->askAndValidate(
760
            $output,
761
            '<question>Please enter the admin\'s lastname:</question> <comment>[' .
762
            $defaults['admin_lastname'] . ']</comment>: ',
763
            $this->notEmptyCallback,
764
            false,
765
            $defaults['admin_lastname']
766
        );
767
768
        $adminEmail = $useDefaultConfigParams ? $defaults['admin_email'] : $dialog->askAndValidate(
769
            $output,
770
            '<question>Please enter the admin\'s email:</question> <comment>[' .
771
            $defaults['admin_email'] . ']</comment>: ',
772
            $this->notEmptyCallback,
773
            false,
774
            $defaults['admin_email']
775
        );
776
777
        $validateBaseUrl = function ($input) {
778
            if (!preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $input)) {
779
                throw new InvalidArgumentException(
780
                    sprintf('Invalid URL %s. Please enter a valid URL', var_export($input, true))
781
                );
782
            }
783
            if (parse_url($input, \PHP_URL_HOST) == 'localhost') {
784
                throw new InvalidArgumentException(
785
                    'localhost cause problems! Please use 127.0.0.1 or another hostname'
786
                );
787
            }
788
789
            return $input;
790
        };
791
792
        $baseUrl = $input->getOption('baseUrl');
793
        if (null === $baseUrl) {
794
            if (!$input->isInteractive()) {
795
                throw new InvalidArgumentException('Installation base url is mandatory, use --baseUrl.');
796
            }
797
            $baseUrl = $dialog->askAndValidate(
798
                $output,
799
                '<question>Please enter the base url:</question> ',
800
                $validateBaseUrl
801
            );
802
        }
803
        $validateBaseUrl($baseUrl);
804
        $baseUrl = rtrim($baseUrl, '/') . '/'; // normalize baseUrl
805
806
        /**
807
         * Correct session save (common mistake)
808
         */
809
        if ($sessionSave == 'file') {
810
            $sessionSave = 'files';
811
        }
812
813
        /**
814
         * Try to create session folder
815
         */
816
        $defaultSessionFolder = $this->config['installationFolder'] . '/' . self::DEFAULT_SESSION_PATH;
817
        if ($sessionSave == 'files' && !is_dir($defaultSessionFolder)) {
818
            @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...
819
        }
820
821
        $dbHost = $this->config['db_host'];
822
        if ($this->config['db_port'] != 3306) {
823
            $dbHost .= ':' . $this->config['db_port'];
824
        }
825
826
        $argv = array(
827
            'license_agreement_accepted' => 'yes',
828
            'locale'                     => $locale,
829
            'timezone'                   => $timezone,
830
            'db_host'                    => $dbHost,
831
            'db_name'                    => $this->config['db_name'],
832
            'db_user'                    => $this->config['db_user'],
833
            'db_pass'                    => $this->config['db_pass'],
834
            'db_prefix'                  => $this->config['db_prefix'],
835
            'url'                        => $baseUrl,
836
            'use_rewrites'               => 'yes',
837
            'use_secure'                 => 'no',
838
            'secure_base_url'            => '',
839
            'use_secure_admin'           => 'no',
840
            'admin_username'             => $adminUsername,
841
            'admin_lastname'             => $adminLastname,
842
            'admin_firstname'            => $adminFirstname,
843
            'admin_email'                => $adminEmail,
844
            'admin_password'             => $adminPassword,
845
            'session_save'               => $sessionSave,
846
            'admin_frontname'            => $adminFrontname, /* magento 1 */
847
            'backend_frontname'          => $adminFrontname, /* magento 2 */
848
            'default_currency'           => $currency,
849
            'skip_url_validation'        => 'yes',
850
        );
851
        if ($useDefaultConfigParams) {
852
            if (strlen($defaults['encryption_key']) > 0) {
853
                $argv['encryption_key'] = $defaults['encryption_key'];
854
            }
855
            if (strlen($defaults['use_secure']) > 0) {
856
                $argv['use_secure'] = $defaults['use_secure'];
857
                $argv['secure_base_url'] = str_replace('http://', 'https://', $baseUrl);
858
            }
859
            if (strlen($defaults['use_rewrites']) > 0) {
860
                $argv['use_rewrites'] = $defaults['use_rewrites'];
861
            }
862
        }
863
864
        $this->runInstallScriptCommand($output, $this->config['installationFolder'], $argv);
865
866
        /* @var $dialog DialogHelper */
867
        $dialog = $this->getHelper('dialog');
868
869
        /**
870
         * Htaccess file
871
         */
872
        if ($input->getOption('useDefaultConfigParams') == null || $input->getOption('replaceHtaccessFile') != null) {
873
            $replaceHtaccessFile = false;
874
875
            if ($this->_parseBoolOption($input->getOption('replaceHtaccessFile'))) {
876
                $replaceHtaccessFile = true;
877
            } elseif ($dialog->askConfirmation(
878
                $output,
879
                '<question>Write BaseURL to .htaccess file?</question> <comment>[n]</comment>: ',
880
                false
881
            )
882
            ) {
883
                $replaceHtaccessFile = true;
884
            }
885
886
            if ($replaceHtaccessFile) {
887
                $this->replaceHtaccessFile($baseUrl);
888
            }
889
        }
890
891
        \chdir($this->config['installationFolder']);
892
        $this->getApplication()->reinit();
893
        $output->writeln('<info>Reindex all after installation</info>');
894
        $this->getApplication()->run(new StringInput('index:reindex:all'), $output);
895
        $this->getApplication()->run(new StringInput('sys:check'), $output);
896
        $output->writeln('<info>Successfully installed magento</info>');
897
    }
898
899
    /**
900
     * Check if we have a magento 2 or 1 installation and return path to install.php
901
     *
902
     * @return string
903
     */
904
    protected function getInstallScriptPath()
905
    {
906
        $magento1InstallScriptPath = $this->config['installationFolder'] . '/' . MAGENTO_INSTALL_SCRIPT_PATH;
907
        $magento2InstallScriptPath = $this->config['installationFolder'] . '/dev/shell/install.php';
908
        if (file_exists($magento2InstallScriptPath)) {
909
            return $magento2InstallScriptPath;
910
        }
911
912
        return $magento1InstallScriptPath;
913
    }
914
915
    /**
916
     * @param string $baseUrl
917
     */
918
    protected function replaceHtaccessFile($baseUrl)
919
    {
920
        $content = file_get_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess');
921
        copy(
922
            $this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess',
923
            $this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess.dist'
924
        );
925
        $content = str_replace('#RewriteBase /magento/', 'RewriteBase ' . parse_url($baseUrl, PHP_URL_PATH), $content);
926
        file_put_contents($this->config['installationFolder'] . DIRECTORY_SEPARATOR . '.htaccess', $content);
927
    }
928
929
    /**
930
     * @param OutputInterface $output
931
     */
932
    protected function setDirectoryPermissions($output)
933
    {
934
        try {
935
            $varFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var';
936
            if (!is_dir($varFolder)) {
937
                @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...
938
            }
939
            @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...
940
941
            $varCacheFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/cache';
942
            if (!is_dir($varCacheFolder)) {
943
                @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...
944
            }
945
            @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...
946
947
            $mediaFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'media';
948
            if (!is_dir($mediaFolder)) {
949
                @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...
950
            }
951
            @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...
952
953
            $finder = Finder::create();
954
            $finder->directories()
955
                ->ignoreUnreadableDirs(true)
956
                ->in(array($varFolder, $mediaFolder));
957
            foreach ($finder as $dir) {
958
                @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...
959
            }
960
        } catch (Exception $e) {
961
            $output->writeln('<error>' . $e->getMessage() . '</error>');
962
        }
963
    }
964
965
    /**
966
     * @return array
967
     */
968
    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...
969
    {
970
        if ($this->_argv === null) {
971
            $this->_argv = $_SERVER['argv'];
972
        }
973
974
        return $this->_argv;
975
    }
976
977
    /**
978
     * @param array $args
979
     */
980
    public function setCliArguments($args)
981
    {
982
        $this->_argv = $args;
983
    }
984
985
    /**
986
     * Invoke Magento PHP install script shell/install.php
987
     *
988
     * @param OutputInterface $output
989
     * @param string $installationFolder folder where magento is installed in, must exists setup script in
990
     * @param array $argv
991
     * @return void
992
     */
993
    private function runInstallScriptCommand(OutputInterface $output, $installationFolder, array $argv)
994
    {
995
        $installArgs = '';
996
        foreach ($argv as $argName => $argValue) {
997
            $installArgs .= '--' . $argName . ' ' . escapeshellarg($argValue) . ' ';
998
        }
999
1000
        $output->writeln('<info>Start installation process.</info>');
1001
1002
        $installCommand = sprintf(
1003
            '%s -ddisplay_startup_errors=1 -ddisplay_errors=1 -derror_reporting=-1 -f %s -- %s',
1004
            OperatingSystem::getPhpBinary(),
1005
            escapeshellarg($installationFolder . '/' . self::MAGENTO_INSTALL_SCRIPT_PATH),
1006
            $installArgs
1007
        );
1008
1009
        $output->writeln('<comment>' . $installCommand . '</comment>');
1010
        $installException = null;
1011
        $installationOutput = null;
1012
        $returnStatus = null;
1013
        try {
1014
            Exec::run($installCommand, $installationOutput, $returnStatus);
1015
        } catch (Exception $installException) {
1016
            /* fall-through intended */
1017
        }
1018
1019
        if (isset($installException) || $returnStatus !== Exec::CODE_CLEAN_EXIT) {
1020
            $this->getApplication()->setAutoExit(true);
1021
            throw new RuntimeException(
1022
                sprintf('Installation failed (Exit code %s). %s', $returnStatus, $installationOutput),
1023
                1,
1024
                $installException
1025
            );
1026
        }
1027
        $output->writeln('<info>Successfully installed Magento</info>');
1028
        $encryptionKey = trim(substr(strstr($installationOutput, ':'), 1));
1029
        $output->writeln('<comment>Encryption Key:</comment> <info>' . $encryptionKey . '</info>');
1030
    }
1031
}
1032