Completed
Pull Request — master (#1013)
by Pierre
03:13
created

SelfUpdateCommand::isOutdatedVersion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace N98\Magento\Command;
4
5
use Composer\Downloader\FilesystemException;
6
use Composer\IO\ConsoleIO;
7
use Composer\Util\RemoteFilesystem;
8
use Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Input\InputOption;
10
use Symfony\Component\Console\Output\OutputInterface;
11
12
/**
13
 * @codeCoverageIgnore
14
 * @author Igor Wiedler <[email protected]>
15
 * @author Christian Münch <[email protected]>
16
 */
17
class SelfUpdateCommand extends AbstractMagentoCommand
18
{
19
    const VERSION_TXT_URL_UNSTABLE = 'https://raw.githubusercontent.com/netz98/n98-magerun/develop/version.txt';
20
    const MAGERUN_DOWNLOAD_URL_UNSTABLE = 'https://files.magerun.net/n98-magerun-dev.phar';
21
    const VERSION_TXT_URL_STABLE = 'https://raw.githubusercontent.com/netz98/n98-magerun/master/version.txt';
22
    const MAGERUN_DOWNLOAD_URL_STABLE = 'https://files.magerun.net/n98-magerun.phar';
23
    const CHANGELOG_DOWNLOAD_URL_UNSTABLE = 'https://raw.github.com/netz98/n98-magerun/develop/CHANGELOG.md';
24
    const CHANGELOG_DOWNLOAD_URL_STABLE = 'https://raw.github.com/netz98/n98-magerun/master/CHANGELOG.md';
25
26
    protected function configure()
27
    {
28
        $this
29
            ->setName('self-update')
30
            ->setAliases(array('selfupdate'))
31
            ->addOption('unstable', null, InputOption::VALUE_NONE, 'Load unstable version from develop branch')
32
            ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Tests if there is a new version without any update.')
33
            ->setDescription('Updates n98-magerun.phar to the latest version.')
34
            ->setHelp(
35
                <<<EOT
36
The <info>self-update</info> command checks github for newer
37
versions of n98-magerun and if found, installs the latest.
38
39
<info>php n98-magerun.phar self-update</info>
40
41
EOT
42
            )
43
        ;
44
    }
45
46
    /**
47
     * @return bool
48
     */
49
    public function isEnabled()
50
    {
51
        return $this->getApplication()->isPharMode();
52
    }
53
54
    /**
55
     * @param \Symfony\Component\Console\Input\InputInterface $input
56
     * @param \Symfony\Component\Console\Output\OutputInterface $output
57
     * @return int|null|void
58
     * @throws \Composer\Downloader\FilesystemException
59
     * @throws \Exception
60
     */
61
    protected function execute(InputInterface $input, OutputInterface $output)
62
    {
63
        $isDryRun = $input->getOption('dry-run');
64
        $localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
65
        $tempFilename = dirname($localFilename) . '/' . basename($localFilename, '.phar') . '-temp.phar';
66
67
        // check for permissions in local filesystem before start connection process
68
        if (!is_writable($tempDirectory = dirname($tempFilename))) {
69
            throw new FilesystemException(
70
                'n98-magerun update failed: the "' . $tempDirectory .
71
                '" directory used to download the temp file could not be written'
72
            );
73
        }
74
75
        if (!is_writable($localFilename)) {
76
            throw new FilesystemException(
77
                'n98-magerun update failed: the "' . $localFilename . '" file could not be written'
78
            );
79
        }
80
81
        $io = new ConsoleIO($input, $output, $this->getHelperSet());
0 ignored issues
show
Bug introduced by
It seems like $this->getHelperSet() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
82
        $rfs = new RemoteFilesystem($io);
83
84
        $loadUnstable = $input->getOption('unstable');
85
        if ($loadUnstable) {
86
            $versionTxtUrl = self::VERSION_TXT_URL_UNSTABLE;
87
            $remoteFilename = self::MAGERUN_DOWNLOAD_URL_UNSTABLE;
88
        } else {
89
            $versionTxtUrl = self::VERSION_TXT_URL_STABLE;
90
            $remoteFilename = self::MAGERUN_DOWNLOAD_URL_STABLE;
91
        }
92
93
        $latest = trim($rfs->getContents('raw.githubusercontent.com', $versionTxtUrl, false));
94
95
        if ($this->isOutdatedVersion($latest, $loadUnstable)) {
96
            $output->writeln(sprintf("Updating to version <info>%s</info>.", $latest));
97
98
            try {
99
                if (!$isDryRun) {
100
                    $this->downloadNewVersion($output, $rfs, $remoteFilename, $tempFilename);
101
                    $this->checkNewPharFile($tempFilename, $localFilename);
102
                }
103
104
                $output->writeln('<info>Successfully updated n98-magerun</info>');
105
                $this->showChangelog($output, $loadUnstable, $rfs);
106
107
                $this->_exit(0);
108
            } catch (\Exception $e) {
109
                @unlink($tempFilename);
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...
110
                if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) {
111
                    throw $e;
112
                }
113
                $output->writeln('<error>The download is corrupted (' . $e->getMessage() . ').</error>');
114
                $output->writeln('<error>Please re-run the self-update command to try again.</error>');
115
            }
116
        } else {
117
            $output->writeln("<info>You are using the latest n98-magerun version.</info>");
118
        }
119
    }
120
121
    /**
122
     * Stop execution
123
     *
124
     * This is a workaround to prevent warning of dispatcher after replacing
125
     * the phar file.
126
     *
127
     * @param int $statusCode
128
     * @return void
129
     */
130
    protected function _exit($statusCode = 0)
131
    {
132
        exit($statusCode);
133
    }
134
135
    /**
136
     * @param \Symfony\Component\Console\Output\OutputInterface $output
137
     * @param $rfs
138
     * @param $remoteFilename
139
     * @param $tempFilename
140
     */
141
    private function downloadNewVersion(OutputInterface $output, $rfs, $remoteFilename, $tempFilename)
142
    {
143
        $rfs->copy('raw.github.com', $remoteFilename, $tempFilename);
144
145
        if (!file_exists($tempFilename)) {
146
            $output->writeln('<error>The download of the new n98-magerun version failed for an unexpected reason');
147
        }
148
    }
149
150
    /**
151
     * @param $tempFilename
152
     * @param $localFilename
153
     */
154
    private function checkNewPharFile($tempFilename, $localFilename)
155
    {
156
        \error_reporting(E_ALL); // supress notices
157
158
        @chmod($tempFilename, 0777 & ~umask());
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...
159
        // test the phar validity
160
        $phar = new \Phar($tempFilename);
161
        // free the variable to unlock the file
162
        unset($phar);
163
        @rename($tempFilename, $localFilename);
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...
164
    }
165
166
    /**
167
     * @param \Symfony\Component\Console\Output\OutputInterface $output
168
     * @param $loadUnstable
169
     * @param $rfs
170
     */
171
    private function showChangelog(OutputInterface $output, $loadUnstable, $rfs)
172
    {
173
        if ($loadUnstable) {
174
            $changeLogContent = $rfs->getContents(
175
                'raw.github.com',
176
                self::CHANGELOG_DOWNLOAD_URL_UNSTABLE,
177
                false
178
            );
179
        } else {
180
            $changeLogContent = $rfs->getContents(
181
                'raw.github.com',
182
                self::CHANGELOG_DOWNLOAD_URL_STABLE,
183
                false
184
            );
185
        }
186
187
        if ($changeLogContent) {
188
            $output->writeln($changeLogContent);
189
        }
190
191
        if ($loadUnstable) {
192
            $unstableFooterMessage = <<<UNSTABLE_FOOTER
193
<comment>
194
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
195
!! DEVELOPMENT VERSION. DO NOT USE IN PRODUCTION !!
196
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
197
</comment>
198
UNSTABLE_FOOTER;
199
            $output->writeln($unstableFooterMessage);
200
        }
201
    }
202
203
    /**
204
     * @param $latest
205
     * @param $loadUnstable
206
     * @return bool
207
     */
208
    private function isOutdatedVersion($latest, $loadUnstable)
209
    {
210
        return $this->getApplication()->getVersion() !== $latest || $loadUnstable;
211
    }
212
}
213