first commit

This commit is contained in:
2019-03-28 17:57:56 +01:00
commit b0e25fd66f
561 changed files with 56803 additions and 0 deletions

View File

@@ -0,0 +1,267 @@
<?php
/**
* @package Grav.Console
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Gpm;
use Grav\Common\Grav;
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class DirectInstallCommand extends ConsoleCommand
{
/**
*
*/
protected function configure()
{
$this
->setName("direct-install")
->setAliases(['directinstall'])
->addArgument(
'package-file',
InputArgument::REQUIRED,
'Installable package local <path> or remote <URL>. Can install specific version'
)
->addOption(
'all-yes',
'y',
InputOption::VALUE_NONE,
'Assumes yes (or best approach) instead of prompting'
)
->addOption(
'destination',
'd',
InputOption::VALUE_OPTIONAL,
'The destination where the package should be installed at. By default this would be where the grav instance has been launched from',
GRAV_ROOT
)
->setDescription("Installs Grav, plugin, or theme directly from a file or a URL")
->setHelp('The <info>direct-install</info> command installs Grav, plugin, or theme directly from a file or a URL');
}
/**
* @return bool
*/
protected function serve()
{
// Making sure the destination is usable
$this->destination = realpath($this->input->getOption('destination'));
if (
!Installer::isGravInstance($this->destination) ||
!Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
) {
$this->output->writeln("<red>ERROR</red>: " . Installer::lastErrorMsg());
exit;
}
$this->all_yes = $this->input->getOption('all-yes');
$package_file = $this->input->getArgument('package-file');
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Are you sure you want to direct-install <cyan>'.$package_file.'</cyan> [y|N] ', false);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln("exiting...");
$this->output->writeln('');
exit;
}
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$tmp_zip = $tmp_dir . '/Grav-' . uniqid();
$this->output->writeln("");
$this->output->writeln("Preparing to install <cyan>" . $package_file . "</cyan>");
if (Response::isRemote($package_file)) {
$this->output->write(" |- Downloading package... 0%");
try {
$zip = GPM::downloadPackage($package_file, $tmp_zip);
} catch (\RuntimeException $e) {
$this->output->writeln('');
$this->output->writeln(" `- <red>ERROR: " . $e->getMessage() . "</red>");
$this->output->writeln('');
exit;
}
if ($zip) {
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... 100%");
$this->output->writeln('');
}
} else {
$this->output->write(" |- Copying package... 0%");
$zip = GPM::copyPackage($package_file, $tmp_zip);
if ($zip) {
$this->output->write("\x0D");
$this->output->write(" |- Copying package... 100%");
$this->output->writeln('');
}
}
if (file_exists($zip)) {
$tmp_source = $tmp_dir . '/Grav-' . uniqid();
$this->output->write(" |- Extracting package... ");
$extracted = Installer::unZip($zip, $tmp_source);
if (!$extracted) {
$this->output->write("\x0D");
$this->output->writeln(" |- Extracting package... <red>failed</red>");
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
$this->output->write("\x0D");
$this->output->writeln(" |- Extracting package... <green>ok</green>");
$type = GPM::getPackageType($extracted);
if (!$type) {
$this->output->writeln(" '- <red>ERROR: Not a valid Grav package</red>");
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
$blueprint = GPM::getBlueprints($extracted);
if ($blueprint) {
if (isset($blueprint['dependencies'])) {
$depencencies = [];
foreach ($blueprint['dependencies'] as $dependency) {
if (is_array($dependency)){
if (isset($dependency['name'])) {
$depencencies[] = $dependency['name'];
}
if (isset($dependency['github'])) {
$depencencies[] = $dependency['github'];
}
} else {
$depencencies[] = $dependency;
}
}
$this->output->writeln(" |- Dependencies found... <cyan>[" . implode(',', $depencencies) . "]</cyan>");
$question = new ConfirmationQuestion(" | '- Dependencies will not be satisfied. Continue ? [y|N] ", false);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln("exiting...");
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
}
}
if ($type == 'grav') {
$this->output->write(" |- Checking destination... ");
Installer::isValidDestination(GRAV_ROOT . '/system');
if (Installer::IS_LINK === Installer::lastErrorCode()) {
$this->output->write("\x0D");
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
$this->output->writeln(" '- <red>ERROR: symlinks found...</red> <yellow>" . GRAV_ROOT."</yellow>");
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
$this->output->write("\x0D");
$this->output->writeln(" |- Checking destination... <green>ok</green>");
$this->output->write(" |- Installing package... ");
Installer::install($zip, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true], $extracted);
} else {
$name = GPM::getPackageName($extracted);
if (!$name) {
$this->output->writeln("<red>ERROR: Name could not be determined.</red> Please specify with --name|-n");
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
$install_path = GPM::getInstallPath($type, $name);
$is_update = file_exists($install_path);
$this->output->write(" |- Checking destination... ");
Installer::isValidDestination(GRAV_ROOT . DS . $install_path);
if (Installer::lastErrorCode() == Installer::IS_LINK) {
$this->output->write("\x0D");
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
$this->output->writeln(" '- <red>ERROR: symlink found...</red> <yellow>" . GRAV_ROOT . DS . $install_path . '</yellow>');
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
} else {
$this->output->write("\x0D");
$this->output->writeln(" |- Checking destination... <green>ok</green>");
}
$this->output->write(" |- Installing package... ");
Installer::install(
$zip,
$this->destination,
$options = [
'install_path' => $install_path,
'theme' => (($type == 'theme')),
'is_update' => $is_update
],
$extracted
);
}
Folder::delete($tmp_source);
$this->output->write("\x0D");
if(Installer::lastErrorCode()) {
$this->output->writeln(" '- <red>" . Installer::lastErrorMsg() . "</red>");
$this->output->writeln('');
} else {
$this->output->writeln(" |- Installing package... <green>ok</green>");
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
} else {
$this->output->writeln(" '- <red>ERROR: ZIP package could not be found</red>");
}
Folder::delete($tmp_zip);
// clear cache after successful upgrade
$this->clearCache();
return true;
}
}

View File

@@ -0,0 +1,272 @@
<?php
/**
* @package Grav.Console
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Common\Utils;
use Grav\Console\ConsoleCommand;
use League\CLImate\CLImate;
use Symfony\Component\Console\Input\InputOption;
class IndexCommand extends ConsoleCommand
{
/**
* @var
*/
protected $data;
/**
* @var
*/
protected $gpm;
/**
* @var
*/
protected $options;
/**
* @var array
*/
protected $sortKeys = ['name', 'slug', 'author', 'date'];
/**
*
*/
protected function configure()
{
$this
->setName("index")
->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'Force re-fetching the data from remote'
)
->addOption(
'filter',
'F',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Allows to limit the results based on one or multiple filters input. This can be either portion of a name/slug or a regex'
)
->addOption(
'themes-only',
'T',
InputOption::VALUE_NONE,
'Filters the results to only Themes'
)
->addOption(
'plugins-only',
'P',
InputOption::VALUE_NONE,
'Filters the results to only Plugins'
)
->addOption(
'updates-only',
'U',
InputOption::VALUE_NONE,
'Filters the results to Updatable Themes and Plugins only'
)
->addOption(
'installed-only',
'I',
InputOption::VALUE_NONE,
'Filters the results to only the Themes and Plugins you have installed'
)
->addOption(
'sort',
's',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Allows to sort (ASC) the results based on one or multiple keys. SORT can be either "name", "slug", "author", "date"',
['date']
)
->addOption(
'desc',
'D',
InputOption::VALUE_NONE,
'Reverses the order of the output.'
)
->setDescription("Lists the plugins and themes available for installation")
->setHelp('The <info>index</info> command lists the plugins and themes available for installation')
;
}
/**
* @return int|null|void
*/
protected function serve()
{
$this->options = $this->input->getOptions();
$this->gpm = new GPM($this->options['force']);
$this->displayGPMRelease();
$this->data = $this->gpm->getRepository();
$data = $this->filter($this->data);
$climate = new CLImate;
$climate->extend('Grav\Console\TerminalObjects\Table');
if (!$data) {
$this->output->writeln('No data was found in the GPM repository stored locally.');
$this->output->writeln('Please try clearing cache and running the <green>bin/gpm index -f</green> command again');
$this->output->writeln('If this doesn\'t work try tweaking your GPM system settings.');
$this->output->writeln('');
$this->output->writeln('For more help go to:');
$this->output->writeln(' -> <yellow>https://learn.getgrav.org/troubleshooting/common-problems#cannot-connect-to-the-gpm</yellow>');
die;
}
foreach ($data as $type => $packages) {
$this->output->writeln("<green>" . strtoupper($type) . "</green> [ " . count($packages) . " ]");
$packages = $this->sort($packages);
if (!empty($packages)) {
$table = [];
$index = 0;
foreach ($packages as $slug => $package) {
$row = [
'Count' => $index++ + 1,
'Name' => "<cyan>" . Utils::truncate($package->name, 20, false, ' ', '...') . "</cyan> ",
'Slug' => $slug,
'Version'=> $this->version($package),
'Installed' => $this->installed($package)
];
$table[] = $row;
}
$climate->table($table);
}
$this->output->writeln('');
}
$this->output->writeln('You can either get more informations about a package by typing:');
$this->output->writeln(' <green>' . $this->argv . ' info <cyan><package></cyan></green>');
$this->output->writeln('');
$this->output->writeln('Or you can install a package by typing:');
$this->output->writeln(' <green>' . $this->argv . ' install <cyan><package></cyan></green>');
$this->output->writeln('');
}
/**
* @param $package
*
* @return string
*/
private function version($package)
{
$list = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}();
$package = isset($list[$package->slug]) ? $list[$package->slug] : $package;
$type = ucfirst(preg_replace("/s$/", '', $package->package_type));
$updatable = $this->gpm->{'is' . $type . 'Updatable'}($package->slug);
$installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug);
$local = $this->gpm->{'getInstalled' . $type}($package->slug);
if (!$installed || !$updatable) {
$version = $installed ? $local->version : $package->version;
return "v<green>" . $version . "</green>";
}
if ($updatable) {
return "v<red>" . $package->version . "</red> <cyan>-></cyan> v<green>" . $package->available . "</green>";
}
return '';
}
/**
* @param $package
*
* @return string
*/
private function installed($package)
{
$package = isset($list[$package->slug]) ? $list[$package->slug] : $package;
$type = ucfirst(preg_replace("/s$/", '', $package->package_type));
$installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug);
return !$installed ? '<magenta>not installed</magenta>' : '<cyan>installed</cyan>';
}
/**
* @param $data
*
* @return mixed
*/
public function filter($data)
{
// filtering and sorting
if ($this->options['plugins-only']) {
unset($data['themes']);
}
if ($this->options['themes-only']) {
unset($data['plugins']);
}
$filter = [
$this->options['filter'],
$this->options['installed-only'],
$this->options['updates-only'],
$this->options['desc']
];
if (count(array_filter($filter))) {
foreach ($data as $type => $packages) {
foreach ($packages as $slug => $package) {
$filter = true;
// Filtering by string
if ($this->options['filter']) {
$filter = preg_grep('/(' . (implode('|', $this->options['filter'])) . ')/i', [$slug, $package->name]);
}
// Filtering updatables only
if ($this->options['installed-only'] && $filter) {
$method = ucfirst(preg_replace("/s$/", '', $package->package_type));
$filter = $this->gpm->{'is' . $method . 'Installed'}($package->slug);
}
// Filtering updatables only
if ($this->options['updates-only'] && $filter) {
$method = ucfirst(preg_replace("/s$/", '', $package->package_type));
$filter = $this->gpm->{'is' . $method . 'Updatable'}($package->slug);
}
if (!$filter) {
unset($data[$type][$slug]);
}
}
}
}
return $data;
}
/**
* @param $packages
*/
public function sort($packages)
{
foreach ($this->options['sort'] as $key) {
$packages = $packages->sort(function ($a, $b) use ($key) {
switch ($key) {
case 'author':
return strcmp($a->{$key}['name'], $b->{$key}['name']);
break;
default:
return strcmp($a->$key, $b->$key);
}
}, $this->options['desc'] ? true : false);
}
return $packages;
}
}

View File

@@ -0,0 +1,181 @@
<?php
/**
* @package Grav.Console
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class InfoCommand extends ConsoleCommand
{
/**
* @var
*/
protected $data;
/**
* @var
*/
protected $gpm;
protected $all_yes;
/**
*
*/
protected function configure()
{
$this
->setName("info")
->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'Force fetching the new data remotely'
)
->addOption(
'all-yes',
'y',
InputOption::VALUE_NONE,
'Assumes yes (or best approach) instead of prompting'
)
->addArgument(
'package',
InputArgument::REQUIRED,
'The package of which more informations are desired. Use the "index" command for a list of packages'
)
->setDescription("Shows more informations about a package")
->setHelp('The <info>info</info> shows more informations about a package');
}
/**
* @return int|null|void
*/
protected function serve()
{
$this->gpm = new GPM($this->input->getOption('force'));
$this->all_yes = $this->input->getOption('all-yes');
$this->displayGPMRelease();
$foundPackage = $this->gpm->findPackage($this->input->getArgument('package'));
if (!$foundPackage) {
$this->output->writeln("The package <cyan>'" . $this->input->getArgument('package') . "'</cyan> was not found in the Grav repository.");
$this->output->writeln('');
$this->output->writeln("You can list all the available packages by typing:");
$this->output->writeln(" <green>" . $this->argv . " index</green>");
$this->output->writeln('');
exit;
}
$this->output->writeln("Found package <cyan>'" . $this->input->getArgument('package') . "'</cyan> under the '<green>" . ucfirst($foundPackage->package_type) . "</green>' section");
$this->output->writeln('');
$this->output->writeln("<cyan>" . $foundPackage->name . "</cyan> [" . $foundPackage->slug . "]");
$this->output->writeln(str_repeat('-', strlen($foundPackage->name) + strlen($foundPackage->slug) + 3));
$this->output->writeln("<white>" . strip_tags($foundPackage->description_plain) . "</white>");
$this->output->writeln('');
$packageURL = '';
if (isset($foundPackage->author['url'])) {
$packageURL = '<' . $foundPackage->author['url'] . '>';
}
$this->output->writeln("<green>" . str_pad("Author",
12) . ":</green> " . $foundPackage->author['name'] . ' <' . $foundPackage->author['email'] . '> ' . $packageURL);
foreach ([
'version',
'keywords',
'date',
'homepage',
'demo',
'docs',
'guide',
'repository',
'bugs',
'zipball_url',
'license'
] as $info) {
if (isset($foundPackage->$info)) {
$name = ucfirst($info);
$data = $foundPackage->$info;
if ($info == 'zipball_url') {
$name = "Download";
}
if ($info == 'date') {
$name = "Last Update";
$data = date('D, j M Y, H:i:s, P ', strtotime('2014-09-16T00:07:16Z'));
}
$name = str_pad($name, 12);
$this->output->writeln("<green>" . $name . ":</green> " . $data);
}
}
$type = rtrim($foundPackage->package_type, 's');
$updatable = $this->gpm->{'is' . $type . 'Updatable'}($foundPackage->slug);
$installed = $this->gpm->{'is' . $type . 'Installed'}($foundPackage->slug);
// display current version if installed and different
if ($installed && $updatable) {
$local = $this->gpm->{'getInstalled'. $type}($foundPackage->slug);
$this->output->writeln('');
$this->output->writeln("Currently installed version: <magenta>" . $local->version . "</magenta>");
$this->output->writeln('');
}
// display changelog information
$questionHelper = $this->getHelper('question');
$question = new ConfirmationQuestion("Would you like to read the changelog? [y|N] ",
false);
$answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
if ($answer) {
$changelog = $foundPackage->changelog;
$this->output->writeln("");
foreach ($changelog as $version => $log) {
$title = $version . ' [' . $log['date'] . ']';
$content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', function ($match) {
return "\n" . ucfirst($match[1]) . ":";
}, $log['content']);
$this->output->writeln('<cyan>'.$title.'</cyan>');
$this->output->writeln(str_repeat('-', strlen($title)));
$this->output->writeln($content);
$this->output->writeln("");
$question = new ConfirmationQuestion("Press [ENTER] to continue or [q] to quit ", true);
$answer = $this->all_yes ? false : $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
break;
}
$this->output->writeln("");
}
}
$this->output->writeln('');
if ($installed && $updatable) {
$this->output->writeln("You can update this package by typing:");
$this->output->writeln(" <green>" . $this->argv . " update</green> <cyan>" . $foundPackage->slug . "</cyan>");
} else {
$this->output->writeln("You can install this package by typing:");
$this->output->writeln(" <green>" . $this->argv . " install</green> <cyan>" . $foundPackage->slug . "</cyan>");
}
$this->output->writeln('');
}
}

View File

@@ -0,0 +1,696 @@
<?php
/**
* @package Grav.Console
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Gpm;
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Licenses;
use Grav\Common\GPM\Response;
use Grav\Common\GPM\Remote\Package as Package;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/(.*)/');
class InstallCommand extends ConsoleCommand
{
/** @var */
protected $data;
/** @var GPM */
protected $gpm;
/** @var */
protected $destination;
/** @var */
protected $file;
/** @var */
protected $tmp;
/** @var */
protected $local_config;
/** @var bool */
protected $use_symlinks;
/** @var array */
protected $demo_processing = [];
protected $all_yes;
/**
*
*/
protected function configure()
{
$this
->setName("install")
->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'Force re-fetching the data from remote'
)
->addOption(
'all-yes',
'y',
InputOption::VALUE_NONE,
'Assumes yes (or best approach) instead of prompting'
)
->addOption(
'destination',
'd',
InputOption::VALUE_OPTIONAL,
'The destination where the package should be installed at. By default this would be where the grav instance has been launched from',
GRAV_ROOT
)
->addArgument(
'package',
InputArgument::IS_ARRAY | InputArgument::REQUIRED,
'Package(s) to install. Use "bin/gpm index" to list packages. Use "bin/gpm direct-install" to install a specific version'
)
->setDescription("Performs the installation of plugins and themes")
->setHelp('The <info>install</info> command allows to install plugins and themes');
}
/**
* Allows to set the GPM object, used for testing the class
*
* @param $gpm
*/
public function setGpm($gpm)
{
$this->gpm = $gpm;
}
/**
* @return bool
*/
protected function serve()
{
$this->gpm = new GPM($this->input->getOption('force'));
$this->all_yes = $this->input->getOption('all-yes');
$this->displayGPMRelease();
$this->destination = realpath($this->input->getOption('destination'));
$packages = array_map('strtolower', $this->input->getArgument('package'));
$this->data = $this->gpm->findPackages($packages);
$this->loadLocalConfig();
if (
!Installer::isGravInstance($this->destination) ||
!Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
) {
$this->output->writeln("<red>ERROR</red>: " . Installer::lastErrorMsg());
exit;
}
$this->output->writeln('');
if (!$this->data['total']) {
$this->output->writeln("Nothing to install.");
$this->output->writeln('');
exit;
}
if (count($this->data['not_found'])) {
$this->output->writeln("These packages were not found on Grav: <red>" . implode('</red>, <red>',
array_keys($this->data['not_found'])) . "</red>");
}
unset($this->data['not_found']);
unset($this->data['total']);
if (isset($this->local_config)) {
// Symlinks available, ask if Grav should use them
$this->use_symlinks = false;
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Should Grav use the symlinks if available? [y|N] ', false);
$answer = $this->all_yes ? false : $helper->ask($this->input, $this->output, $question);
if ($answer) {
$this->use_symlinks = true;
}
}
$this->output->writeln('');
try {
$dependencies = $this->gpm->getDependencies($packages);
} catch (\Exception $e) {
//Error out if there are incompatible packages requirements and tell which ones, and what to do
//Error out if there is any error in parsing the dependencies and their versions, and tell which one is broken
$this->output->writeln("<red>" . $e->getMessage() . "</red>");
return false;
}
if ($dependencies) {
try {
$this->installDependencies($dependencies, 'install', "The following dependencies need to be installed...");
$this->installDependencies($dependencies, 'update', "The following dependencies need to be updated...");
$this->installDependencies($dependencies, 'ignore', "The following dependencies can be updated as there is a newer version, but it's not mandatory...", false);
} catch (\Exception $e) {
$this->output->writeln("<red>Installation aborted</red>");
return false;
}
$this->output->writeln("<green>Dependencies are OK</green>");
$this->output->writeln("");
}
//We're done installing dependencies. Install the actual packages
foreach ($this->data as $data) {
foreach ($data as $package_name => $package) {
if (array_key_exists($package_name, $dependencies)) {
$this->output->writeln("<green>Package " . $package_name . " already installed as dependency</green>");
} else {
$is_valid_destination = Installer::isValidDestination($this->destination . DS . $package->install_path);
if ($is_valid_destination || Installer::lastErrorCode() == Installer::NOT_FOUND) {
$this->processPackage($package, false);
} else {
if (Installer::lastErrorCode() == Installer::EXISTS) {
try {
$this->askConfirmationIfMajorVersionUpdated($package);
$this->gpm->checkNoOtherPackageNeedsThisDependencyInALowerVersion($package->slug, $package->available, array_keys($data));
} catch (\Exception $e) {
$this->output->writeln("<red>" . $e->getMessage() . "</red>");
return false;
}
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion("The package <cyan>$package_name</cyan> is already installed, overwrite? [y|N] ", false);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if ($answer) {
$is_update = true;
$this->processPackage($package, $is_update);
} else {
$this->output->writeln("<yellow>Package " . $package_name . " not overwritten</yellow>");
}
} else {
if (Installer::lastErrorCode() == Installer::IS_LINK) {
$this->output->writeln("<red>Cannot overwrite existing symlink for </red><cyan>$package_name</cyan>");
$this->output->writeln("");
}
}
}
}
}
}
if (count($this->demo_processing) > 0) {
foreach ($this->demo_processing as $package) {
$this->installDemoContent($package);
}
}
// clear cache after successful upgrade
$this->clearCache();
return true;
}
/**
* If the package is updated from an older major release, show warning and ask confirmation
*
* @param $package
*/
public function askConfirmationIfMajorVersionUpdated($package)
{
$helper = $this->getHelper('question');
$package_name = $package->name;
$new_version = $package->available ? $package->available : $this->gpm->getLatestVersionOfPackage($package->slug);
$old_version = $package->version;
$major_version_changed = explode('.', $new_version)[0] !== explode('.', $old_version)[0];
if ($major_version_changed) {
if ($this->all_yes) {
$this->output->writeln("The package <cyan>$package_name</cyan> will be updated to a new major version <green>$new_version</green>, from <magenta>$old_version</magenta>");
return;
}
$question = new ConfirmationQuestion("The package <cyan>$package_name</cyan> will be updated to a new major version <green>$new_version</green>, from <magenta>$old_version</magenta>. Be sure to read what changed with the new major release. Continue? [y|N] ", false);
if (!$helper->ask($this->input, $this->output, $question)) {
$this->output->writeln("<yellow>Package " . $package_name . " not updated</yellow>");
exit;
}
}
}
/**
* Given a $dependencies list, filters their type according to $type and
* shows $message prior to listing them to the user. Then asks the user a confirmation prior
* to installing them.
*
* @param array $dependencies The dependencies array
* @param string $type The type of dependency to show: install, update, ignore
* @param string $message A message to be shown prior to listing the dependencies
* @param bool $required A flag that determines if the installation is required or optional
*
* @throws \Exception
*/
public function installDependencies($dependencies, $type, $message, $required = true)
{
$packages = array_filter($dependencies, function ($action) use ($type) { return $action === $type; });
if (count($packages) > 0) {
$this->output->writeln($message);
foreach ($packages as $dependencyName => $dependencyVersion) {
$this->output->writeln(" |- Package <cyan>" . $dependencyName . "</cyan>");
}
$this->output->writeln("");
$helper = $this->getHelper('question');
if ($type == 'install') {
$questionAction = 'Install';
} else {
$questionAction = 'Update';
}
if (count($packages) == 1) {
$questionArticle = 'this';
} else {
$questionArticle = 'these';
}
if (count($packages) == 1) {
$questionNoun = 'package';
} else {
$questionNoun = 'packages';
}
$question = new ConfirmationQuestion("$questionAction $questionArticle $questionNoun? [Y|n] ", true);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if ($answer) {
foreach ($packages as $dependencyName => $dependencyVersion) {
$package = $this->gpm->findPackage($dependencyName);
$this->processPackage($package, ($type == 'update') ? true : false);
}
$this->output->writeln('');
} else {
if ($required) {
throw new \Exception();
}
}
}
}
/**
* @param $package
* @param bool $is_update True if the package is an update
*/
private function processPackage($package, $is_update = false)
{
if (!$package) {
$this->output->writeln("<red>Package not found on the GPM!</red> ");
$this->output->writeln('');
return;
}
$symlink = false;
if ($this->use_symlinks) {
if ($this->getSymlinkSource($package) || !isset($package->version)) {
$symlink = true;
}
}
$symlink ? $this->processSymlink($package) : $this->processGpm($package, $is_update);
$this->processDemo($package);
}
/**
* Add package to the queue to process the demo content, if demo content exists
*
* @param $package
*/
private function processDemo($package)
{
$demo_dir = $this->destination . DS . $package->install_path . DS . '_demo';
if (file_exists($demo_dir)) {
$this->demo_processing[] = $package;
}
}
/**
* Prompt to install the demo content of a package
*
* @param $package
*/
private function installDemoContent($package)
{
$demo_dir = $this->destination . DS . $package->install_path . DS . '_demo';
if (file_exists($demo_dir)) {
$dest_dir = $this->destination . DS . 'user';
$pages_dir = $dest_dir . DS . 'pages';
// Demo content exists, prompt to install it.
$this->output->writeln("<white>Attention: </white><cyan>" . $package->name . "</cyan> contains demo content");
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Do you wish to install this demo content? [y|N] ', false);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln(" '- <red>Skipped!</red> ");
$this->output->writeln('');
return;
}
// if pages folder exists in demo
if (file_exists($demo_dir . DS . 'pages')) {
$pages_backup = 'pages.' . date('m-d-Y-H-i-s');
$question = new ConfirmationQuestion('This will backup your current `user/pages` folder to `user/' . $pages_backup . '`, continue? [y|N]', false);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln(" '- <red>Skipped!</red> ");
$this->output->writeln('');
return;
}
// backup current pages folder
if (file_exists($dest_dir)) {
if (rename($pages_dir, $dest_dir . DS . $pages_backup)) {
$this->output->writeln(" |- Backing up pages... <green>ok</green>");
} else {
$this->output->writeln(" |- Backing up pages... <red>failed</red>");
}
}
}
// Confirmation received, copy over the data
$this->output->writeln(" |- Installing demo content... <green>ok</green> ");
Folder::rcopy($demo_dir, $dest_dir);
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
}
/**
* @param $package
*
* @return array|bool
*/
private function getGitRegexMatches($package)
{
if (isset($package->repository)) {
$repository = $package->repository;
} else {
return false;
}
preg_match(GIT_REGEX, $repository, $matches);
return $matches;
}
/**
* @param $package
*
* @return bool|string
*/
private function getSymlinkSource($package)
{
$matches = $this->getGitRegexMatches($package);
foreach ($this->local_config as $paths) {
if (Utils::endsWith($matches[2], '.git')) {
$repo_dir = preg_replace('/\.git$/', '', $matches[2]);
} else {
$repo_dir = $matches[2];
}
$paths = (array) $paths;
foreach ($paths as $repo) {
$path = rtrim($repo, '/') . '/' . $repo_dir;
if (file_exists($path)) {
return $path;
}
}
}
return false;
}
/**
* @param $package
*/
private function processSymlink($package)
{
exec('cd ' . $this->destination);
$to = $this->destination . DS . $package->install_path;
$from = $this->getSymlinkSource($package);
$this->output->writeln("Preparing to Symlink <cyan>" . $package->name . "</cyan>");
$this->output->write(" |- Checking source... ");
if (file_exists($from)) {
$this->output->writeln("<green>ok</green>");
$this->output->write(" |- Checking destination... ");
$checks = $this->checkDestination($package);
if (!$checks) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
if (file_exists($to)) {
$this->output->writeln(" '- <red>Symlink cannot overwrite an existing package, please remove first</red>");
$this->output->writeln('');
} else {
symlink($from, $to);
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Symlinking package... <green>ok</green> ");
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
}
return;
}
$this->output->writeln("<red>not found!</red>");
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
}
/**
* @param $package
* @param bool $is_update
*
* @return bool
*/
private function processGpm($package, $is_update = false)
{
$version = isset($package->available) ? $package->available : $package->version;
$license = Licenses::get($package->slug);
$this->output->writeln("Preparing to install <cyan>" . $package->name . "</cyan> [v" . $version . "]");
$this->output->write(" |- Downloading package... 0%");
$this->file = $this->downloadPackage($package, $license);
if (!$this->file) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
return false;
}
$this->output->write(" |- Checking destination... ");
$checks = $this->checkDestination($package);
if (!$checks) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
$this->output->write(" |- Installing package... ");
$installation = $this->installPackage($package, $is_update);
if (!$installation) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
return true;
}
}
return false;
}
/**
* @param Package $package
*
* @param string $license
*
* @return string
*/
private function downloadPackage($package, $license = null)
{
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$this->tmp = $tmp_dir . '/Grav-' . uniqid();
$filename = $package->slug . basename($package->zipball_url);
$filename = preg_replace('/[\\\\\/:"*?&<>|]+/mi', '-', $filename);
$query = '';
if ($package->premium) {
$query = \json_encode(array_merge(
$package->premium,
[
'slug' => $package->slug,
'filename' => $package->premium['filename'],
'license_key' => $license
]
));
$query = '?d=' . base64_encode($query);
}
try {
$output = Response::get($package->zipball_url . $query, [], [$this, 'progress']);
} catch (\Exception $e) {
$error = str_replace("\n", "\n | '- ", $e->getMessage());
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Downloading package... <red>error</red> ");
$this->output->writeln(" | '- " . $error);
return false;
}
Folder::mkdir($this->tmp);
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... 100%");
$this->output->writeln('');
file_put_contents($this->tmp . DS . $filename, $output);
return $this->tmp . DS . $filename;
}
/**
* @param $package
*
* @return bool
*/
private function checkDestination($package)
{
$question_helper = $this->getHelper('question');
Installer::isValidDestination($this->destination . DS . $package->install_path);
if (Installer::lastErrorCode() == Installer::IS_LINK) {
$this->output->write("\x0D");
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
if ($this->all_yes) {
$this->output->writeln(" | '- <yellow>Skipped automatically.</yellow>");
return false;
}
$question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ",
false);
$answer = $question_helper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln(" | '- <red>You decided to not delete the symlink automatically.</red>");
return false;
} else {
unlink($this->destination . DS . $package->install_path);
}
}
$this->output->write("\x0D");
$this->output->writeln(" |- Checking destination... <green>ok</green>");
return true;
}
/**
* Install a package
*
* @param Package $package
* @param bool $is_update True if it's an update. False if it's an install
*
* @return bool
*/
private function installPackage($package, $is_update = false)
{
$type = $package->package_type;
Installer::install($this->file, $this->destination, ['install_path' => $package->install_path, 'theme' => (($type == 'themes')), 'is_update' => $is_update]);
$error_code = Installer::lastErrorCode();
Folder::delete($this->tmp);
if ($error_code) {
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Installing package... <red>error</red> ");
$this->output->writeln(" | '- " . Installer::lastErrorMsg());
return false;
}
$message = Installer::getMessage();
if ($message) {
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- " . $message);
}
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Installing package... <green>ok</green> ");
return true;
}
/**
* @param $progress
*/
public function progress($progress)
{
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... " . str_pad($progress['percent'], 5, " ",
STR_PAD_LEFT) . '%');
}
}

View File

@@ -0,0 +1,262 @@
<?php
/**
* @package Grav.Console
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Gpm;
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Common\GPM\Upgrader;
use Grav\Common\Grav;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class SelfupgradeCommand extends ConsoleCommand
{
/**
* @var
*/
protected $data;
/**
* @var
*/
protected $extensions;
/**
* @var
*/
protected $updatable;
/**
* @var
*/
protected $file;
/**
* @var array
*/
protected $types = ['plugins', 'themes'];
/**
* @var
*/
private $tmp;
/**
* @var
*/
private $upgrader;
protected $all_yes;
protected $overwrite;
/**
*
*/
protected function configure()
{
$this
->setName("self-upgrade")
->setAliases(['selfupgrade', 'selfupdate'])
->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'Force re-fetching the data from remote'
)
->addOption(
'all-yes',
'y',
InputOption::VALUE_NONE,
'Assumes yes (or best approach) instead of prompting'
)
->addOption(
'overwrite',
'o',
InputOption::VALUE_NONE,
'Option to overwrite packages if they already exist'
)
->setDescription("Detects and performs an update of Grav itself when available")
->setHelp('The <info>update</info> command updates Grav itself when a new version is available');
}
/**
* @return int|null|void
*/
protected function serve()
{
$this->upgrader = new Upgrader($this->input->getOption('force'));
$this->all_yes = $this->input->getOption('all-yes');
$this->overwrite = $this->input->getOption('overwrite');
$this->displayGPMRelease();
$update = $this->upgrader->getAssets()['grav-update'];
$local = $this->upgrader->getLocalVersion();
$remote = $this->upgrader->getRemoteVersion();
$release = strftime('%c', strtotime($this->upgrader->getReleaseDate()));
if (!$this->upgrader->meetsRequirements()) {
$this->output->writeln("<red>ATTENTION:</red>");
$this->output->writeln(" Grav has increased the minimum PHP requirement.");
$this->output->writeln(" You are currently running PHP <red>" . phpversion() . "</red>, but PHP <green>" . $this->upgrader->minPHPVersion() . "</green> is required.");
$this->output->writeln(" Additional information: <white>http://getgrav.org/blog/changing-php-requirements</white>");
$this->output->writeln("");
$this->output->writeln("Selfupgrade aborted.");
$this->output->writeln("");
exit;
}
if (!$this->overwrite && !$this->upgrader->isUpgradable()) {
$this->output->writeln("You are already running the latest version of Grav (v" . $local . ") released on " . $release);
exit;
}
Installer::isValidDestination(GRAV_ROOT . '/system');
if (Installer::IS_LINK === Installer::lastErrorCode()) {
$this->output->writeln("<red>ATTENTION:</red> Grav is symlinked, cannot upgrade, aborting...");
$this->output->writeln('');
$this->output->writeln("You are currently running a symbolically linked Grav v" . $local . ". Latest available is v". $remote . ".");
exit;
}
// not used but preloaded just in case!
new ArrayInput([]);
$questionHelper = $this->getHelper('question');
$this->output->writeln("Grav v<cyan>$remote</cyan> is now available [release date: $release].");
$this->output->writeln("You are currently using v<cyan>" . GRAV_VERSION . "</cyan>.");
if (!$this->all_yes) {
$question = new ConfirmationQuestion("Would you like to read the changelog before proceeding? [y|N] ",
false);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if ($answer) {
$changelog = $this->upgrader->getChangelog(GRAV_VERSION);
$this->output->writeln("");
foreach ($changelog as $version => $log) {
$title = $version . ' [' . $log['date'] . ']';
$content = preg_replace_callback('/\d\.\s\[\]\(#(.*)\)/', function ($match) {
return "\n" . ucfirst($match[1]) . ":";
}, $log['content']);
$this->output->writeln($title);
$this->output->writeln(str_repeat('-', strlen($title)));
$this->output->writeln($content);
$this->output->writeln("");
}
$question = new ConfirmationQuestion("Press [ENTER] to continue.", true);
$questionHelper->ask($this->input, $this->output, $question);
}
$question = new ConfirmationQuestion("Would you like to upgrade now? [y|N] ", false);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln("Aborting...");
exit;
}
}
$this->output->writeln("");
$this->output->writeln("Preparing to upgrade to v<cyan>$remote</cyan>..");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update['size']) . "]... 0%");
$this->file = $this->download($update);
$this->output->write(" |- Installing upgrade... ");
$installation = $this->upgrade();
if (!$installation) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
// clear cache after successful upgrade
$this->clearCache('all');
}
/**
* @param $package
*
* @return string
*/
private function download($package)
{
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$this->tmp = $tmp_dir . '/Grav-' . uniqid();
$output = Response::get($package['download'], [], [$this, 'progress']);
Folder::mkdir($this->tmp);
$this->output->write("\x0D");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package['size']) . "]... 100%");
$this->output->writeln('');
file_put_contents($this->tmp . DS . $package['name'], $output);
return $this->tmp . DS . $package['name'];
}
/**
* @return bool
*/
private function upgrade()
{
Installer::install($this->file, GRAV_ROOT,
['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true]);
$errorCode = Installer::lastErrorCode();
Folder::delete($this->tmp);
if ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)) {
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Installing upgrade... <red>error</red> ");
$this->output->writeln(" | '- " . Installer::lastErrorMsg());
return false;
}
$this->output->write("\x0D");
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Installing upgrade... <green>ok</green> ");
return true;
}
/**
* @param $progress
*/
public function progress($progress)
{
$this->output->write("\x0D");
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($progress["filesize"]) . "]... " . str_pad($progress['percent'],
5, " ", STR_PAD_LEFT) . '%');
}
/**
* @param $size
* @param int $precision
*
* @return string
*/
public function formatBytes($size, $precision = 2)
{
$base = log($size) / log(1024);
$suffixes = array('', 'k', 'M', 'G', 'T');
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[(int)floor($base)];
}
}

View File

@@ -0,0 +1,302 @@
<?php
/**
* @package Grav.Console
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Common\Grav;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class UninstallCommand extends ConsoleCommand
{
/**
* @var
*/
protected $data;
/** @var GPM */
protected $gpm;
/**
* @var
*/
protected $destination;
/**
* @var
*/
protected $file;
/**
* @var
*/
protected $tmp;
protected $dependencies= [];
protected $all_yes;
/**
*
*/
protected function configure()
{
$this
->setName("uninstall")
->addOption(
'all-yes',
'y',
InputOption::VALUE_NONE,
'Assumes yes (or best approach) instead of prompting'
)
->addArgument(
'package',
InputArgument::IS_ARRAY | InputArgument::REQUIRED,
'The package(s) that are desired to be removed. Use the "index" command for a list of packages'
)
->setDescription("Performs the uninstallation of plugins and themes")
->setHelp('The <info>uninstall</info> command allows to uninstall plugins and themes');
}
/**
* @return int|null|void
*/
protected function serve()
{
$this->gpm = new GPM();
$this->all_yes = $this->input->getOption('all-yes');
$packages = array_map('strtolower', $this->input->getArgument('package'));
$this->data = ['total' => 0, 'not_found' => []];
foreach ($packages as $package) {
$plugin = $this->gpm->getInstalledPlugin($package);
$theme = $this->gpm->getInstalledTheme($package);
if ($plugin || $theme) {
$this->data[strtolower($package)] = $plugin ?: $theme;
$this->data['total']++;
} else {
$this->data['not_found'][] = $package;
}
}
$this->output->writeln('');
if (!$this->data['total']) {
$this->output->writeln("Nothing to uninstall.");
$this->output->writeln('');
exit;
}
if (count($this->data['not_found'])) {
$this->output->writeln("These packages were not found installed: <red>" . implode('</red>, <red>',
$this->data['not_found']) . "</red>");
}
unset($this->data['not_found']);
unset($this->data['total']);
foreach ($this->data as $slug => $package) {
$this->output->writeln("Preparing to uninstall <cyan>" . $package->name . "</cyan> [v" . $package->version . "]");
$this->output->write(" |- Checking destination... ");
$checks = $this->checkDestination($slug, $package);
if (!$checks) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
$uninstall = $this->uninstallPackage($slug, $package);
if (!$uninstall) {
$this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
} else {
$this->output->writeln(" '- <green>Success!</green> ");
}
}
}
// clear cache after successful upgrade
$this->clearCache();
}
/**
* @param $slug
* @param $package
*
* @return bool
*/
private function uninstallPackage($slug, $package, $is_dependency = false)
{
if (!$slug) {
return false;
}
//check if there are packages that have this as a dependency. Abort and show list
$dependent_packages = $this->gpm->getPackagesThatDependOnPackage($slug);
if (count($dependent_packages) > ($is_dependency ? 1 : 0)) {
$this->output->writeln('');
$this->output->writeln('');
$this->output->writeln("<red>Uninstallation failed.</red>");
$this->output->writeln('');
if (count($dependent_packages) > ($is_dependency ? 2 : 1)) {
$this->output->writeln("The installed packages <cyan>" . implode('</cyan>, <cyan>', $dependent_packages) . "</cyan> depends on this package. Please remove those first.");
} else {
$this->output->writeln("The installed package <cyan>" . implode('</cyan>, <cyan>', $dependent_packages) . "</cyan> depends on this package. Please remove it first.");
}
$this->output->writeln('');
return false;
}
if (isset($package->dependencies)) {
$dependencies = $package->dependencies;
if ($is_dependency) {
foreach ($dependencies as $key => $dependency) {
if (in_array($dependency['name'], $this->dependencies)) {
unset($dependencies[$key]);
}
}
} else {
if (count($dependencies) > 0) {
$this->output->writeln(' `- Dependencies found...');
$this->output->writeln('');
}
}
$questionHelper = $this->getHelper('question');
foreach ($dependencies as $dependency) {
$this->dependencies[] = $dependency['name'];
if (is_array($dependency)) {
$dependency = $dependency['name'];
}
if ($dependency === 'grav' || $dependency === 'php') {
continue;
}
$dependencyPackage = $this->gpm->findPackage($dependency);
$dependency_exists = $this->packageExists($dependency, $dependencyPackage);
if ($dependency_exists == Installer::EXISTS) {
$this->output->writeln("A dependency on <cyan>" . $dependencyPackage->name . "</cyan> [v" . $dependencyPackage->version . "] was found");
$question = new ConfirmationQuestion(" |- Uninstall <cyan>" . $dependencyPackage->name . "</cyan>? [y|N] ", false);
$answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
if ($answer) {
$uninstall = $this->uninstallPackage($dependency, $dependencyPackage, true);
if (!$uninstall) {
$this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
} else {
$this->output->writeln(" '- <green>Success!</green> ");
}
$this->output->writeln('');
} else {
$this->output->writeln(" '- <yellow>You decided not to uninstall " . $dependencyPackage->name . ".</yellow>");
$this->output->writeln('');
}
}
}
}
$locator = Grav::instance()['locator'];
$path = $locator->findResource($package->package_type . '://' . $slug);
Installer::uninstall($path);
$errorCode = Installer::lastErrorCode();
if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) {
$this->output->writeln(" |- Uninstalling " . $package->name . " package... <red>error</red> ");
$this->output->writeln(" | '- <yellow>" . Installer::lastErrorMsg()."</yellow>");
return false;
}
$message = Installer::getMessage();
if ($message) {
$this->output->writeln(" |- " . $message);
}
if (!$is_dependency && $this->dependencies) {
$this->output->writeln("Finishing up uninstalling <cyan>" . $package->name . "</cyan>");
}
$this->output->writeln(" |- Uninstalling " . $package->name . " package... <green>ok</green> ");
return true;
}
/**
* @param $slug
* @param $package
*
* @return bool
*/
private function checkDestination($slug, $package)
{
$questionHelper = $this->getHelper('question');
$exists = $this->packageExists($slug, $package);
if ($exists == Installer::IS_LINK) {
$this->output->write("\x0D");
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
if ($this->all_yes) {
$this->output->writeln(" | '- <yellow>Skipped automatically.</yellow>");
return false;
}
$question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ",
false);
$answer = $this->all_yes ? true : $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln(" | '- <red>You decided not to delete the symlink automatically.</red>");
return false;
}
}
$this->output->write("\x0D");
$this->output->writeln(" |- Checking destination... <green>ok</green>");
return true;
}
/**
* Check if package exists
*
* @param $slug
* @param $package
* @return int
*/
private function packageExists($slug, $package)
{
$path = Grav::instance()['locator']->findResource($package->package_type . '://' . $slug);
Installer::isValidDestination($path);
return Installer::lastErrorCode();
}
}

View File

@@ -0,0 +1,285 @@
<?php
/**
* @package Grav.Console
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Console\ConsoleCommand;
use Grav\Common\GPM\Upgrader;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class UpdateCommand extends ConsoleCommand
{
/**
* @var
*/
protected $data;
/**
* @var
*/
protected $extensions;
/**
* @var
*/
protected $updatable;
/**
* @var
*/
protected $destination;
/**
* @var
*/
protected $file;
/**
* @var array
*/
protected $types = ['plugins', 'themes'];
/**
* @var GPM $gpm
*/
protected $gpm;
protected $all_yes;
protected $overwrite;
/**
* @var Upgrader
*/
protected $upgrader;
/**
*
*/
protected function configure()
{
$this
->setName("update")
->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'Force re-fetching the data from remote'
)
->addOption(
'destination',
'd',
InputOption::VALUE_OPTIONAL,
'The grav instance location where the updates should be applied to. By default this would be where the grav cli has been launched from',
GRAV_ROOT
)
->addOption(
'all-yes',
'y',
InputOption::VALUE_NONE,
'Assumes yes (or best approach) instead of prompting'
)
->addOption(
'overwrite',
'o',
InputOption::VALUE_NONE,
'Option to overwrite packages if they already exist'
)
->addOption(
'plugins',
'p',
InputOption::VALUE_NONE,
'Update only plugins'
)
->addOption(
'themes',
't',
InputOption::VALUE_NONE,
'Update only themes'
)
->addArgument(
'package',
InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
'The package or packages that is desired to update. By default all available updates will be applied.'
)
->setDescription("Detects and performs an update of plugins and themes when available")
->setHelp('The <info>update</info> command updates plugins and themes when a new version is available');
}
/**
* @return int|null|void
*/
protected function serve()
{
$this->upgrader = new Upgrader($this->input->getOption('force'));
$local = $this->upgrader->getLocalVersion();
$remote = $this->upgrader->getRemoteVersion();
if ($local !== $remote) {
$this->output->writeln("<yellow>WARNING</yellow>: A new version of Grav is available. You should update Grav before updating plugins and themes. If you continue without updating Grav, some plugins or themes may stop working.");
$this->output->writeln("");
$questionHelper = $this->getHelper('question');
$question = new ConfirmationQuestion("Continue with the update process? [Y|n] ", true);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln("<red>Update aborted. Exiting...</red>");
exit;
}
}
$this->gpm = new GPM($this->input->getOption('force'));
$this->all_yes = $this->input->getOption('all-yes');
$this->overwrite = $this->input->getOption('overwrite');
$this->displayGPMRelease();
$this->destination = realpath($this->input->getOption('destination'));
if (!Installer::isGravInstance($this->destination)) {
$this->output->writeln("<red>ERROR</red>: " . Installer::lastErrorMsg());
exit;
}
if ($this->input->getOption('plugins') === false && $this->input->getOption('themes') === false) {
$list_type = ['plugins' => true, 'themes' => true];
} else {
$list_type['plugins'] = $this->input->getOption('plugins');
$list_type['themes'] = $this->input->getOption('themes');
}
if ($this->overwrite) {
$this->data = $this->gpm->getInstallable($list_type);
$description = " can be overwritten";
} else {
$this->data = $this->gpm->getUpdatable($list_type);
$description = " need updating";
}
$only_packages = array_map('strtolower', $this->input->getArgument('package'));
if (!$this->overwrite && !$this->data['total']) {
$this->output->writeln("Nothing to update.");
exit;
}
$this->output->write("Found <green>" . $this->gpm->countInstalled() . "</green> packages installed of which <magenta>" . $this->data['total'] . "</magenta>" . $description);
$limit_to = $this->userInputPackages($only_packages);
$this->output->writeln('');
unset($this->data['total']);
unset($limit_to['total']);
// updates review
$slugs = [];
$index = 0;
foreach ($this->data as $packages) {
foreach ($packages as $slug => $package) {
if (count($only_packages) && !array_key_exists($slug, $limit_to)) {
continue;
}
if (!$package->available) {
$package->available = $package->version;
}
$this->output->writeln(
// index
str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . ". " .
// name
"<cyan>" . str_pad($package->name, 15) . "</cyan> " .
// version
"[v<magenta>" . $package->version . "</magenta> -> v<green>" . $package->available . "</green>]"
);
$slugs[] = $slug;
}
}
if (!$this->all_yes) {
// prompt to continue
$this->output->writeln("");
$questionHelper = $this->getHelper('question');
$question = new ConfirmationQuestion("Continue with the update process? [Y|n] ", true);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln("<red>Update aborted. Exiting...</red>");
exit;
}
}
// finally update
$install_command = $this->getApplication()->find('install');
$args = new ArrayInput([
'command' => 'install',
'package' => $slugs,
'-f' => $this->input->getOption('force'),
'-d' => $this->destination,
'-y' => true
]);
$command_exec = $install_command->run($args, $this->output);
if ($command_exec != 0) {
$this->output->writeln("<red>Error:</red> An error occurred while trying to install the packages");
exit;
}
}
/**
* @param $only_packages
*
* @return array
*/
private function userInputPackages($only_packages)
{
$found = ['total' => 0];
$ignore = [];
if (!count($only_packages)) {
$this->output->writeln('');
} else {
foreach ($only_packages as $only_package) {
$find = $this->gpm->findPackage($only_package);
if (!$find || (!$this->overwrite && !$this->gpm->isUpdatable($find->slug))) {
$name = isset($find->slug) ? $find->slug : $only_package;
$ignore[$name] = $name;
} else {
$found[$find->slug] = $find;
$found['total']++;
}
}
if ($found['total']) {
$list = $found;
unset($list['total']);
$list = array_keys($list);
if ($found['total'] !== $this->data['total']) {
$this->output->write(", only <magenta>" . $found['total'] . "</magenta> will be updated");
}
$this->output->writeln('');
$this->output->writeln("Limiting updates for only <cyan>" . implode('</cyan>, <cyan>',
$list) . "</cyan>");
}
if (count($ignore)) {
$this->output->writeln('');
$this->output->writeln("Packages not found or not requiring updates: <red>" . implode('</red>, <red>',
$ignore) . "</red>");
}
}
return $found;
}
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* @package Grav.Console
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Upgrader;
use Grav\Console\ConsoleCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class VersionCommand extends ConsoleCommand
{
/**
* @var GPM
*/
protected $gpm;
/**
*
*/
protected function configure()
{
$this
->setName("version")
->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'Force re-fetching the data from remote'
)
->addArgument(
'package',
InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
'The package or packages that is desired to know the version of. By default and if not specified this would be grav'
)
->setDescription("Shows the version of an installed package. If available also shows pending updates.")
->setHelp('The <info>version</info> command displays the current version of a package installed and, if available, the available version of pending updates');
}
/**
* @return int|null|void
*/
protected function serve()
{
$this->gpm = new GPM($this->input->getOption('force'));
$packages = $this->input->getArgument('package');
$installed = false;
if (!count($packages)) {
$packages = ['grav'];
}
foreach ($packages as $package) {
$package = strtolower($package);
$name = null;
$version = null;
$updatable = false;
if ($package == 'grav') {
$name = 'Grav';
$version = GRAV_VERSION;
$upgrader = new Upgrader();
if ($upgrader->isUpgradable()) {
$updatable = ' [upgradable: v<green>' . $upgrader->getRemoteVersion() . '</green>]';
}
} else {
// get currently installed version
$locator = \Grav\Common\Grav::instance()['locator'];
$blueprints_path = $locator->findResource('plugins://' . $package . DS . 'blueprints.yaml');
if (!file_exists($blueprints_path)) { // theme?
$blueprints_path = $locator->findResource('themes://' . $package . DS . 'blueprints.yaml');
if (!file_exists($blueprints_path)) {
continue;
}
}
$file = YamlFile::instance($blueprints_path);
$package_yaml = $file->content();
$file->free();
$version = $package_yaml['version'];
if (!$version) {
continue;
}
$installed = $this->gpm->findPackage($package);
if ($installed) {
$name = $installed->name;
if ($this->gpm->isUpdatable($package)) {
$updatable = ' [updatable: v<green>' . $installed->available . '</green>]';
}
}
}
$updatable = $updatable ?: '';
if ($installed || $package == 'grav') {
$this->output->writeln('You are running <white>' . $name . '</white> v<cyan>' . $version . '</cyan>' . $updatable);
} else {
$this->output->writeln('Package <red>' . $package . '</red> not found');
}
}
}
}