*/
class SelfUpdate extends Command
{
/**
* Class constants
*/
const NAME = 'Codeception';
const GITHUB_REPO = 'Codeception/Codeception';
const PHAR_URL = 'http://codeception.com/releases/%s/codecept.phar';
const PHAR_URL_PHP54 = 'http://codeception.com/releases/%s/php54/codecept.phar';
/**
* Holds the current script filename.
* @var string
*/
protected $filename;
/**
* Holds the live version string.
* @var string
*/
protected $liveVersion;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->filename = $_SERVER['argv'][0];
$this
// ->setAliases(array('selfupdate'))
->setDescription(
sprintf(
'Upgrade %s to the latest version',
$this->filename
)
);
parent::configure();
}
/**
* @return string
*/
protected function getCurrentVersion()
{
return Codecept::VERSION;
}
/**
* {@inheritdoc}
*/
public function execute(InputInterface $input, OutputInterface $output)
{
$version = $this->getCurrentVersion();
$output->writeln(
sprintf(
'%s version %s',
self::NAME,
$version
)
);
$output->writeln("\nChecking for a new version...\n");
try {
$latestVersion = $this->getLatestStableVersion();
if ($this->isOutOfDate($version, $latestVersion)) {
$output->writeln(
sprintf(
'A newer version is available: %s',
$latestVersion
)
);
if (!$input->getOption('no-interaction')) {
$dialog = $this->getHelperSet()->get('question');
$question = new ConfirmationQuestion("\nDo you want to update? ", false);
if (!$dialog->ask($input, $output, $question)) {
$output->writeln("\nBye-bye!\n");
return;
}
}
$output->writeln("\nUpdating...");
$this->retrievePharFile($latestVersion, $output);
} else {
$output->writeln('You are already using the latest version.');
}
} catch (\Exception $e) {
$output->writeln(
sprintf(
"\n%s\n",
$e->getMessage()
)
);
}
}
/**
* Checks whether the provided version is current.
*
* @param string $version The version number to check.
* @param string $latestVersion Latest stable version
* @return boolean Returns True if a new version is available.
*/
private function isOutOfDate($version, $latestVersion)
{
return -1 != version_compare($version, $latestVersion, '>=');
}
/**
* @return string
*/
private function getLatestStableVersion()
{
$stableVersions = $this->filterStableVersions(
$this->getGithubTags(self::GITHUB_REPO)
);
return array_reduce(
$stableVersions,
function ($a, $b) {
return version_compare($a, $b, '>') ? $a : $b;
}
);
}
/**
* @param array $tags
* @return array
*/
private function filterStableVersions($tags)
{
return array_filter($tags, function ($tag) {
return preg_match('/^[0-9]+\.[0-9]+\.[0-9]+$/', $tag);
});
}
/**
* Returns an array of tags from a github repo.
*
* @param string $repo The repository name to check upon.
* @return array
*/
protected function getGithubTags($repo)
{
$jsonTags = $this->retrieveContentFromUrl(
'https://api.github.com/repos/' . $repo . '/tags'
);
return array_map(
function ($tag) {
return $tag['name'];
},
json_decode($jsonTags, true)
);
}
/**
* Retrieves the body-content from the provided URL.
*
* @param string $url
* @return string
* @throws \Exception if status code is above 300
*/
private function retrieveContentFromUrl($url)
{
$ctx = $this->prepareContext($url);
$body = file_get_contents($url, 0, $ctx);
if (isset($http_response_header)) {
$code = substr($http_response_header[0], 9, 3);
if (floor($code / 100) > 3) {
throw new \Exception($http_response_header[0]);
}
} else {
throw new \Exception('Request failed.');
}
return $body;
}
/**
* Add proxy support to context if environment variable was set up
*
* @param array $opt context options
* @param string $url
*/
private function prepareProxy(&$opt, $url)
{
$scheme = parse_url($url)['scheme'];
if ($scheme === 'http' && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) {
$proxy = !empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY'];
}
if ($scheme === 'https' && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) {
$proxy = !empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY'];
}
if (!empty($proxy)) {
$proxy = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $proxy);
$opt['http']['proxy'] = $proxy;
}
}
/**
* Preparing context for request
* @param $url
*
* @return resource
*/
private function prepareContext($url)
{
$opts = [
'http' => [
'follow_location' => 1,
'max_redirects' => 20,
'timeout' => 10,
'user_agent' => self::NAME
]
];
$this->prepareProxy($opts, $url);
return stream_context_create($opts);
}
/**
* Retrieves the latest phar file.
*
* @param string $version
* @param OutputInterface $output
* @throws \Exception
*/
protected function retrievePharFile($version, OutputInterface $output)
{
$temp = basename($this->filename, '.phar') . '-temp.phar';
try {
$sourceUrl = $this->getPharUrl($version);
if (@copy($sourceUrl, $temp)) {
chmod($temp, 0777 & ~umask());
// test the phar validity
$phar = new \Phar($temp);
// free the variable to unlock the file
unset($phar);
rename($temp, $this->filename);
} else {
throw new \Exception('Request failed.');
}
} catch (\Exception $e) {
if (!$e instanceof \UnexpectedValueException
&& !$e instanceof \PharException
) {
throw $e;
}
unlink($temp);
$output->writeln(
sprintf(
"\nSomething went wrong (%s).\nPlease re-run this again.\n",
$e->getMessage()
)
);
}
$output->writeln(
sprintf(
"\n%s has been updated.\n",
$this->filename
)
);
}
/**
* Returns Phar file URL for specified version
*
* @param string $version
* @return string
*/
protected function getPharUrl($version)
{
$sourceUrl = self::PHAR_URL;
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
$sourceUrl = self::PHAR_URL_PHP54;
}
return sprintf($sourceUrl, $version);
}
}