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,90 @@
<?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\Cli;
use Grav\Common\Grav;
use Grav\Common\Backup\ZipBackup;
use Grav\Console\ConsoleCommand;
use RocketTheme\Toolbox\File\JsonFile;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
class BackupCommand extends ConsoleCommand
{
/** @var string $source */
protected $source;
/** @var ProgressBar $progress */
protected $progress;
/**
*
*/
protected function configure()
{
$this
->setName("backup")
->addArgument(
'destination',
InputArgument::OPTIONAL,
'Where to store the backup (/backup is default)'
)
->setDescription("Creates a backup of the Grav instance")
->setHelp('The <info>backup</info> creates a zipped backup. Optionally can be saved in a different destination.');
$this->source = getcwd();
}
/**
* @return int|null|void
*/
protected function serve()
{
$this->progress = new ProgressBar($this->output);
$this->progress->setFormat('Archiving <cyan>%current%</cyan> files [<green>%bar%</green>] %elapsed:6s% %memory:6s%');
Grav::instance()['config']->init();
$destination = ($this->input->getArgument('destination')) ? $this->input->getArgument('destination') : null;
$log = JsonFile::instance(Grav::instance()['locator']->findResource("log://backup.log", true, true));
$backup = ZipBackup::backup($destination, [$this, 'output']);
$log->content([
'time' => time(),
'location' => $backup
]);
$log->save();
$this->output->writeln('');
$this->output->writeln('');
}
/**
* @param $args
*/
public function output($args)
{
switch ($args['type']) {
case 'message':
$this->output->writeln($args['message']);
break;
case 'progress':
if ($args['complete']) {
$this->progress->finish();
} else {
$this->progress->advance();
}
break;
}
}
}

View File

@@ -0,0 +1,278 @@
<?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\Cli;
use Grav\Common\Filesystem\Folder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
class CleanCommand extends Command
{
/* @var InputInterface $output */
protected $input;
/* @var OutputInterface $output */
protected $output;
/**
* @var array
*/
protected $paths_to_remove = [
'codeception.yml',
'tests/',
'user/plugins/admin/vendor/bacon/bacon-qr-code/tests',
'user/plugins/admin/vendor/bacon/bacon-qr-code/.gitignore',
'user/plugins/admin/vendor/bacon/bacon-qr-code/.travis.yml',
'user/plugins/admin/vendor/bacon/bacon-qr-code/composer.json',
'user/plugins/admin/vendor/robthree/twofactorauth/demo',
'user/plugins/admin/vendor/robthree/twofactorauth/.vs',
'user/plugins/admin/vendor/robthree/twofactorauth/tests',
'user/plugins/admin/vendor/robthree/twofactorauth/.gitignore',
'user/plugins/admin/vendor/robthree/twofactorauth/.travis.yml',
'user/plugins/admin/vendor/robthree/twofactorauth/composer.json',
'user/plugins/admin/vendor/robthree/twofactorauth/composer.lock',
'user/plugins/admin/vendor/robthree/twofactorauth/logo.png',
'user/plugins/admin/vendor/robthree/twofactorauth/multifactorauthforeveryone.png',
'user/plugins/admin/vendor/robthree/twofactorauth/TwoFactorAuth.phpproj',
'user/plugins/admin/vendor/robthree/twofactorauth/TwoFactorAuth.sin',
'user/plugins/admin/vendor/zendframework/zendxml/tests',
'user/plugins/admin/vendor/zendframework/zendxml/.gitignore',
'user/plugins/admin/vendor/zendframework/zendxml/.travis.yml',
'user/plugins/admin/vendor/zendframework/zendxml/composer.json',
'user/plugins/email/vendor/swiftmailer/swiftmailer/.travis.yml',
'user/plugins/email/vendor/swiftmailer/swiftmailer/build.xml',
'user/plugins/email/vendor/swiftmailer/swiftmailer/composer.json',
'user/plugins/email/vendor/swiftmailer/swiftmailer/create_pear_package.php',
'user/plugins/email/vendor/swiftmailer/swiftmailer/package.xml.tpl',
'user/plugins/email/vendor/swiftmailer/swiftmailer/.gitattributes',
'user/plugins/email/vendor/swiftmailer/swiftmailer/.gitignore',
'user/plugins/email/vendor/swiftmailer/swiftmailer/README.git',
'user/plugins/email/vendor/swiftmailer/swiftmailer/tests',
'user/plugins/email/vendor/swiftmailer/swiftmailer/test-suite',
'user/plugins/email/vendor/swiftmailer/swiftmailer/notes',
'user/plugins/email/vendor/swiftmailer/swiftmailer/doc',
'user/themes/antimatter/.sass-cache',
'vendor/antoligy/dom-string-iterators/composer.json',
'vendor/doctrine/cache/.travis.yml',
'vendor/doctrine/cache/build.properties',
'vendor/doctrine/cache/build.xml',
'vendor/doctrine/cache/composer.json',
'vendor/doctrine/cache/phpunit.xml.dist',
'vendor/doctrine/cache/.coveralls.yml',
'vendor/doctrine/cache/.gitignore',
'vendor/doctrine/cache/.git',
'vendor/doctrine/cache/tests',
'vendor/doctrine/collections/composer.json',
'vendor/doctrine/collections/phpunit.xml.dist',
'vendor/doctrine/collections/tests',
'vendor/donatj/phpuseragentparser/.git',
'vendor/donatj/phpuseragentparser/.gitignore',
'vendor/donatj/phpuseragentparser/.travis.yml',
'vendor/donatj/phpuseragentparser/composer.json',
'vendor/donatj/phpuseragentparser/phpunit.xml.dist',
'vendor/donatj/phpuseragentparser/Tests',
'vendor/donatj/phpuseragentparser/Tools',
'vendor/erusev/parsedown/composer.json',
'vendor/erusev/parsedown/phpunit.xml.dist',
'vendor/erusev/parsedown/.travis.yml',
'vendor/erusev/parsedown/.git',
'vendor/erusev/parsedown/test',
'vendor/erusev/parsedown-extra/composer.json',
'vendor/erusev/parsedown-extra/phpunit.xml.dist',
'vendor/erusev/parsedown-extra/.travis.yml',
'vendor/erusev/parsedown-extra/.git',
'vendor/erusev/parsedown-extra/test',
'vendor/filp/whoops/composer.json',
'vendor/filp/whoops/docs',
'vendor/filp/whoops/examples',
'vendor/filp/whoops/tests',
'vendor/filp/whoops/.git',
'vendor/filp/whoops/.gitignore',
'vendor/filp/whoops/.scrutinizer.yml',
'vendor/filp/whoops/.travis.yml',
'vendor/filp/whoops/phpunit.xml.dist',
'vendor/gregwar/image/Gregwar/Image/composer.json',
'vendor/gregwar/image/Gregwar/Image/phpunit.xml',
'vendor/gregwar/image/Gregwar/Image/.gitignore',
'vendor/gregwar/image/Gregwar/Image/.git',
'vendor/gregwar/image/Gregwar/Image/doc',
'vendor/gregwar/image/Gregwar/Image/demo',
'vendor/gregwar/image/Gregwar/Image/tests',
'vendor/gregwar/cache/Gregwar/Cache/composer.json',
'vendor/gregwar/cache/Gregwar/Cache/phpunit.xml',
'vendor/gregwar/cache/Gregwar/Cache/.gitignore',
'vendor/gregwar/cache/Gregwar/Cache/.git',
'vendor/gregwar/cache/Gregwar/Cache/demo',
'vendor/gregwar/cache/Gregwar/Cache/tests',
'vendor/ircmaxell/password-compat/composer.json',
'vendor/ircmaxell/password-compat/phpunit.xml.dist',
'vendor/ircmaxell/password-compat/version-test.php',
'vendor/ircmaxell/password-compat/.travis.yml',
'vendor/ircmaxell/password-compat/test',
'vendor/league/climate/composer.json',
'vendor/matthiasmullie/minify/bin',
'vendor/matthiasmullie/minify/composer.json',
'vendor/matthiasmullie/minify/docker-composer.yml',
'vendor/matthiasmullie/minify/Dockerfile',
'vendor/matthiasmullie/minify/CONTRIBUTING.md',
'vendor/matthiasmullie/path-converter/composer.json',
'vendor/maximebf/debugbar/bower.json',
'vendor/maximebf/debugbar/composer.json',
'vendor/maximebf/debugbar/.bowerrc',
'vendor/maximebf/debugbar/src/DebugBar/Resources/vendor',
'vendor/maximebf/debugbar/demo',
'vendor/maximebf/debugbar/docs',
'vendor/maximebf/debugbar/tests',
'vendor/maximebf/debugbar/phpunit.xml.dist',
'vendor/miljar/php-exif/.coveralls.yml',
'vendor/miljar/php-exif/.gitignore',
'vendor/miljar/php-exif/.travis.yml',
'vendor/miljar/php-exif/composer.json',
'vendor/miljar/php-exif/composer.lock',
'vendor/miljar/php-exif/phpunit.xml.dist',
'vendor/miljar/php-exif/Resources',
'vendor/miljar/php-exif/tests',
'vendor/monolog/monolog/composer.json',
'vendor/monolog/monolog/doc',
'vendor/monolog/monolog/phpunit.xml.dist',
'vendor/monolog/monolog/.php_cs',
'vendor/monolog/monolog/tests',
'vendor/pimple/pimple/.gitignore',
'vendor/pimple/pimple/.travis.yml',
'vendor/pimple/pimple/composer.json',
'vendor/pimple/pimple/ext',
'vendor/pimple/pimple/phpunit.xml.dist',
'vendor/pimple/pimple/src/Pimple/Tests',
'vendor/psr/container/composer.json',
'vendor/psr/container/.gitignore',
'vendor/psr/simple-cache/composer.json',
'vendor/psr/log/composer.json',
'vendor/psr/log/.gitignore',
'vendor/rockettheme/toolbox/.git',
'vendor/rockettheme/toolbox/.gitignore',
'vendor/rockettheme/toolbox/.scrutinizer.yml',
'vendor/rockettheme/toolbox/.travis.yml',
'vendor/rockettheme/toolbox/composer.json',
'vendor/rockettheme/toolbox/phpunit.xml',
'vendor/seld/cli-prompt/composer.json',
'vendor/seld/cli-prompt/.gitignore',
'vendor/symfony/console/composer.json',
'vendor/symfony/console/phpunit.xml.dist',
'vendor/symfony/console/.gitignore',
'vendor/symfony/console/.git',
'vendor/symfony/console/Tester',
'vendor/symfony/console/Tests',
'vendor/symfony/debug/.gitignore',
'vendor/symfony/debug/.git',
'vendor/symfony/debug/phpunit.xml.dist',
'vendor/symfony/debug/composer.json',
'vendor/symfony/debug/Tests',
'vendor/symfony/debug/Resources',
'vendor/symfony/event-dispatcher/.git',
'vendor/symfony/event-dispatcher/.gitignore',
'vendor/symfony/event-dispatcher/composer.json',
'vendor/symfony/event-dispatcher/phpunit.xml.dist',
'vendor/symfony/event-dispatcher/Tests',
'vendor/symfony/polyfill-iconv/.git',
'vendor/symfony/polyfill-iconv/.gitignore',
'vendor/symfony/polyfill-iconv/composer.json',
'vendor/symfony/polyfill-mbstring/.git',
'vendor/symfony/polyfill-mbstring/.gitignore',
'vendor/symfony/polyfill-mbstring/composer.json',
'vendor/symfony/var-dumper/.git',
'vendor/symfony/var-dumper/.gitignore',
'vendor/symfony/var-dumper/composer.json',
'vendor/symfony/var-dumper/phpunit.xml.dist',
'vendor/symfony/var-dumper/Test',
'vendor/symfony/var-dumper/Tests',
'vendor/symfony/yaml/composer.json',
'vendor/symfony/yaml/phpunit.xml.dist',
'vendor/symfony/yaml/.gitignore',
'vendor/symfony/yaml/.git',
'vendor/symfony/yaml/Tests',
'vendor/twig/twig/.editorconfig',
'vendor/twig/twig/.php_cs.dist',
'vendor/twig/twig/.travis.yml',
'vendor/twig/twig/.gitignore',
'vendor/twig/twig/.git',
'vendor/twig/twig/composer.json',
'vendor/twig/twig/phpunit.xml.dist',
'vendor/twig/twig/doc',
'vendor/twig/twig/ext',
'vendor/twig/twig/test',
];
/**
*
*/
protected function configure()
{
$this
->setName("clean")
->setDescription("Handles cleaning chores for Grav distribution")
->setHelp('The <info>clean</info> clean extraneous folders and data');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$this->cleanPaths();
}
private function cleanPaths()
{
$this->output->writeln('');
$this->output->writeln('<red>DELETING</red>');
$anything = false;
foreach ($this->paths_to_remove as $path) {
$path = ROOT_DIR . $path;
if (is_dir($path) && @Folder::delete($path)) {
$anything = true;
$this->output->writeln('<red>dir: </red>' . $path);
} elseif (is_file($path) && @unlink($path)) {
$anything = true;
$this->output->writeln('<red>file: </red>' . $path);
}
}
if (!$anything) {
$this->output->writeln('');
$this->output->writeln('<green>Nothing to clean...</green>');
}
}
/**
* Set colors style definition for the formatter.
*
* @param InputInterface $input
* @param OutputInterface $output
*/
public function setupConsole(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->output = $output;
$this->output->getFormatter()->setStyle('normal', new OutputFormatterStyle('white'));
$this->output->getFormatter()->setStyle('yellow', new OutputFormatterStyle('yellow', null, ['bold']));
$this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, ['bold']));
$this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan', null, ['bold']));
$this->output->getFormatter()->setStyle('green', new OutputFormatterStyle('green', null, ['bold']));
$this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta', null, ['bold']));
$this->output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, ['bold']));
}
}

View File

@@ -0,0 +1,70 @@
<?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\Cli;
use Grav\Common\Cache;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputOption;
class ClearCacheCommand extends ConsoleCommand
{
/**
*
*/
protected function configure()
{
$this
->setName('clear-cache')
->setAliases(['clearcache'])
->setDescription('Clears Grav cache')
->addOption('all', null, InputOption::VALUE_NONE, 'If set will remove all including compiled, twig, doctrine caches')
->addOption('assets-only', null, InputOption::VALUE_NONE, 'If set will remove only assets/*')
->addOption('images-only', null, InputOption::VALUE_NONE, 'If set will remove only images/*')
->addOption('cache-only', null, InputOption::VALUE_NONE, 'If set will remove only cache/*')
->addOption('tmp-only', null, InputOption::VALUE_NONE, 'If set will remove only tmp/*')
->setHelp('The <info>clear-cache</info> deletes all cache files');
}
/**
* @return int|null|void
*/
protected function serve()
{
$this->cleanPaths();
}
/**
* loops over the array of paths and deletes the files/folders
*/
private function cleanPaths()
{
$this->output->writeln('');
$this->output->writeln('<magenta>Clearing cache</magenta>');
$this->output->writeln('');
if ($this->input->getOption('all')) {
$remove = 'all';
} elseif ($this->input->getOption('assets-only')) {
$remove = 'assets-only';
} elseif ($this->input->getOption('images-only')) {
$remove = 'images-only';
} elseif ($this->input->getOption('cache-only')) {
$remove = 'cache-only';
} elseif ($this->input->getOption('tmp-only')) {
$remove = 'tmp-only';
} else {
$remove = 'standard';
}
foreach (Cache::clearCache($remove) as $result) {
$this->output->writeln($result);
}
}
}

View File

@@ -0,0 +1,72 @@
<?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\Cli;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputOption;
class ComposerCommand extends ConsoleCommand
{
/**
* @var
*/
protected $config;
/**
* @var
*/
protected $local_config;
/**
* @var
*/
protected $destination;
/**
* @var
*/
protected $user_path;
/**
*
*/
protected function configure()
{
$this
->setName("composer")
->addOption(
'install',
'i',
InputOption::VALUE_NONE,
'install the dependencies'
)
->addOption(
'update',
'u',
InputOption::VALUE_NONE,
'update the dependencies'
)
->setDescription("Updates the composer vendor dependencies needed by Grav.")
->setHelp('The <info>composer</info> command updates the composer vendor dependencies needed by Grav');
}
/**
* @return int|null|void
*/
protected function serve()
{
$action = $this->input->getOption('install') ? 'install' : ($this->input->getOption('update') ? 'update' : 'install');
if ($this->input->getOption('install')) {
$action = 'install';
}
// Updates composer first
$this->output->writeln("\nInstalling vendor dependencies");
$this->output->writeln($this->composerUpdate(GRAV_ROOT, $action));
}
}

View File

@@ -0,0 +1,186 @@
<?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\Cli;
use Grav\Console\ConsoleCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class InstallCommand extends ConsoleCommand
{
/**
* @var
*/
protected $config;
/**
* @var
*/
protected $local_config;
/**
* @var
*/
protected $destination;
/**
* @var
*/
protected $user_path;
/**
*
*/
protected function configure()
{
$this
->setName("install")
->addOption(
'symlink',
's',
InputOption::VALUE_NONE,
'Symlink the required bits'
)
->addArgument(
'destination',
InputArgument::OPTIONAL,
'Where to install the required bits (default to current project)'
)
->setDescription("Installs the dependencies needed by Grav. Optionally can create symbolic links")
->setHelp('The <info>install</info> command installs the dependencies needed by Grav. Optionally can create symbolic links');
}
/**
* @return int|null|void
*/
protected function serve()
{
$dependencies_file = '.dependencies';
$this->destination = ($this->input->getArgument('destination')) ? $this->input->getArgument('destination') : ROOT_DIR;
// fix trailing slash
$this->destination = rtrim($this->destination, DS) . DS;
$this->user_path = $this->destination . USER_PATH;
if ($local_config_file = $this->loadLocalConfig()) {
$this->output->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
}
// Look for dependencies file in ROOT and USER dir
if (file_exists($this->user_path . $dependencies_file)) {
$file = YamlFile::instance($this->user_path . $dependencies_file);
} elseif (file_exists($this->destination . $dependencies_file)) {
$file = YamlFile::instance($this->destination . $dependencies_file);
} else {
$this->output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
if ($this->input->getArgument('destination')) {
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install a plugin or a theme? Make sure you use <cyan>bin/gpm install <something></cyan>, not <cyan>bin/grav install</cyan>. This command is only used to install Grav skeletons.');
} else {
$this->output->writeln('<yellow>HINT</yellow> <info>Are you trying to install Grav? Grav is already installed. You need to run this command only if you download a skeleton from GitHub directly.');
}
return;
}
$this->config = $file->content();
$file->free();
// If yaml config, process
if ($this->config) {
if (!$this->input->getOption('symlink')) {
// Updates composer first
$this->output->writeln("\nInstalling vendor dependencies");
$this->output->writeln($this->composerUpdate(GRAV_ROOT, 'install'));
$this->gitclone();
} else {
$this->symlink();
}
} else {
$this->output->writeln('<red>ERROR</red> invalid YAML in ' . $dependencies_file);
}
}
/**
* Clones from Git
*/
private function gitclone()
{
$this->output->writeln('');
$this->output->writeln('<green>Cloning Bits</green>');
$this->output->writeln('============');
$this->output->writeln('');
foreach ($this->config['git'] as $repo => $data) {
$this->destination = rtrim($this->destination, DS);
$path = $this->destination . DS . $data['path'];
if (!file_exists($path)) {
exec('cd "' . $this->destination . '" && git clone -b ' . $data['branch'] . ' --depth 1 ' . $data['url'] . ' ' . $data['path'], $output, $return);
if (!$return) {
$this->output->writeln('<green>SUCCESS</green> cloned <magenta>' . $data['url'] . '</magenta> -> <cyan>' . $path . '</cyan>');
} else {
$this->output->writeln('<red>ERROR</red> cloning <magenta>' . $data['url']);
}
$this->output->writeln('');
} else {
$this->output->writeln('<red>' . $path . ' already exists, skipping...</red>');
$this->output->writeln('');
}
}
}
/**
* Symlinks
*/
private function symlink()
{
$this->output->writeln('');
$this->output->writeln('<green>Symlinking Bits</green>');
$this->output->writeln('===============');
$this->output->writeln('');
if (!$this->local_config) {
$this->output->writeln('<red>No local configuration available, aborting...</red>');
$this->output->writeln('');
return;
}
exec('cd ' . $this->destination);
foreach ($this->config['links'] as $repo => $data) {
$repos = (array) $this->local_config[$data['scm'] . '_repos'];
$from = false;
$to = $this->destination . $data['path'];
foreach ($repos as $repo) {
$path = $repo . $data['src'];
if (file_exists($path)) {
$from = $path;
continue;
}
}
if (!$from) {
$this->output->writeln('<red>source for ' . $data['src'] . ' does not exists, skipping...</red>');
$this->output->writeln('');
} else {
if (!file_exists($to)) {
symlink($from, $to);
$this->output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
$this->output->writeln('');
} else {
$this->output->writeln('<red>destination: ' . $to . ' already exists, skipping...</red>');
$this->output->writeln('');
}
}
}
}
}

View File

@@ -0,0 +1,65 @@
<?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\Cli;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class NewProjectCommand extends ConsoleCommand
{
/**
*
*/
protected function configure()
{
$this
->setName('new-project')
->setAliases(['newproject'])
->addArgument(
'destination',
InputArgument::REQUIRED,
'The destination directory of your new Grav project'
)
->addOption(
'symlink',
's',
InputOption::VALUE_NONE,
'Symlink the required bits'
)
->setDescription('Creates a new Grav project with all the dependencies installed')
->setHelp("The <info>new-project</info> command is a combination of the `setup` and `install` commands.\nCreates a new Grav instance and performs the installation of all the required dependencies.");
}
/**
* @return int|null|void
*/
protected function serve()
{
$sandboxCommand = $this->getApplication()->find('sandbox');
$installCommand = $this->getApplication()->find('install');
$sandboxArguments = new ArrayInput([
'command' => 'sandbox',
'destination' => $this->input->getArgument('destination'),
'-s' => $this->input->getOption('symlink')
]);
$installArguments = new ArrayInput([
'command' => 'install',
'destination' => $this->input->getArgument('destination'),
'-s' => $this->input->getOption('symlink')
]);
$sandboxCommand->run($sandboxArguments, $this->output);
$installCommand->run($installArguments, $this->output);
}
}

View File

@@ -0,0 +1,304 @@
<?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\Cli;
use Grav\Console\ConsoleCommand;
use Grav\Common\Filesystem\Folder;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
class SandboxCommand extends ConsoleCommand
{
/**
* @var array
*/
protected $directories = [
'/assets',
'/backup',
'/cache',
'/images',
'/logs',
'/tmp',
'/user/accounts',
'/user/config',
'/user/data',
'/user/pages',
'/user/plugins',
'/user/themes',
];
/**
* @var array
*/
protected $files = [
'/.dependencies',
'/.htaccess',
'/user/config/site.yaml',
'/user/config/system.yaml',
];
/**
* @var array
*/
protected $mappings = [
'/.gitignore' => '/.gitignore',
'/CHANGELOG.md' => '/CHANGELOG.md',
'/LICENSE.txt' => '/LICENSE.txt',
'/README.md' => '/README.md',
'/CONTRIBUTING.md' => '/CONTRIBUTING.md',
'/index.php' => '/index.php',
'/composer.json' => '/composer.json',
'/bin' => '/bin',
'/system' => '/system',
'/vendor' => '/vendor',
'/webserver-configs' => '/webserver-configs',
];
/**
* @var string
*/
protected $default_file = "---\ntitle: HomePage\n---\n# HomePage\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque porttitor eu felis sed ornare. Sed a mauris venenatis, pulvinar velit vel, dictum enim. Phasellus ac rutrum velit. Nunc lorem purus, hendrerit sit amet augue aliquet, iaculis ultricies nisl. Suspendisse tincidunt euismod risus, quis feugiat arcu tincidunt eget. Nulla eros mi, commodo vel ipsum vel, aliquet congue odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque velit orci, laoreet at adipiscing eu, interdum quis nibh. Nunc a accumsan purus.";
protected $source;
protected $destination;
/**
*
*/
protected function configure()
{
$this
->setName('sandbox')
->setDescription('Setup of a base Grav system in your webroot, good for development, playing around or starting fresh')
->addArgument(
'destination',
InputArgument::REQUIRED,
'The destination directory to symlink into'
)
->addOption(
'symlink',
's',
InputOption::VALUE_NONE,
'Symlink the base grav system'
)
->setHelp("The <info>sandbox</info> command help create a development environment that can optionally use symbolic links to link the core of grav to the git cloned repository.\nGood for development, playing around or starting fresh");
$this->source = getcwd();
}
/**
* @return int|null|void
*/
protected function serve()
{
$this->destination = $this->input->getArgument('destination');
// Symlink the Core Stuff
if ($this->input->getOption('symlink')) {
// Create Some core stuff if it doesn't exist
$this->createDirectories();
// Loop through the symlink mappings and create the symlinks
$this->symlink();
// Copy the Core STuff
} else {
// Create Some core stuff if it doesn't exist
$this->createDirectories();
// Loop through the symlink mappings and copy what otherwise would be symlinks
$this->copy();
}
$this->pages();
$this->initFiles();
$this->perms();
}
/**
*
*/
private function createDirectories()
{
$this->output->writeln('');
$this->output->writeln('<comment>Creating Directories</comment>');
$dirs_created = false;
if (!file_exists($this->destination)) {
mkdir($this->destination, 0777, true);
}
foreach ($this->directories as $dir) {
if (!file_exists($this->destination . $dir)) {
$dirs_created = true;
$this->output->writeln(' <cyan>' . $dir . '</cyan>');
mkdir($this->destination . $dir, 0777, true);
}
}
if (!$dirs_created) {
$this->output->writeln(' <red>Directories already exist</red>');
}
}
/**
*
*/
private function copy()
{
$this->output->writeln('');
$this->output->writeln('<comment>Copying Files</comment>');
foreach ($this->mappings as $source => $target) {
if ((int)$source == $source) {
$source = $target;
}
$from = $this->source . $source;
$to = $this->destination . $target;
$this->output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
@Folder::rcopy($from, $to);
}
}
/**
*
*/
private function symlink()
{
$this->output->writeln('');
$this->output->writeln('<comment>Resetting Symbolic Links</comment>');
foreach ($this->mappings as $source => $target) {
if ((int)$source == $source) {
$source = $target;
}
$from = $this->source . $source;
$to = $this->destination . $target;
$this->output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
if (is_dir($to)) {
@Folder::delete($to);
} else {
@unlink($to);
}
symlink($from, $to);
}
}
/**
*
*/
private function initFiles()
{
$this->check();
$this->output->writeln('');
$this->output->writeln('<comment>File Initializing</comment>');
$files_init = false;
// Copy files if they do not exist
foreach ($this->files as $source => $target) {
if ((int)$source == $source) {
$source = $target;
}
$from = $this->source . $source;
$to = $this->destination . $target;
if (!file_exists($to)) {
$files_init = true;
copy($from, $to);
$this->output->writeln(' <cyan>' . $target . '</cyan> <comment>-></comment> Created');
}
}
if (!$files_init) {
$this->output->writeln(' <red>Files already exist</red>');
}
}
/**
*
*/
private function pages()
{
$this->output->writeln('');
$this->output->writeln('<comment>Pages Initializing</comment>');
// get pages files and initialize if no pages exist
$pages_dir = $this->destination . '/user/pages';
$pages_files = array_diff(scandir($pages_dir), ['..', '.']);
if (count($pages_files) == 0) {
$destination = $this->source . '/user/pages';
Folder::rcopy($destination, $pages_dir);
$this->output->writeln(' <cyan>' . $destination . '</cyan> <comment>-></comment> Created');
}
}
/**
*
*/
private function perms()
{
$this->output->writeln('');
$this->output->writeln('<comment>Permissions Initializing</comment>');
$dir_perms = 0755;
$binaries = glob($this->destination . DS . 'bin' . DS . '*');
foreach ($binaries as $bin) {
chmod($bin, $dir_perms);
$this->output->writeln(' <cyan>bin/' . basename($bin) . '</cyan> permissions reset to ' . decoct($dir_perms));
}
$this->output->writeln("");
}
/**
*
*/
private function check()
{
$success = true;
if (!file_exists($this->destination)) {
$this->output->writeln(' file: <red>$this->destination</red> does not exist!');
$success = false;
}
foreach ($this->directories as $dir) {
if (!file_exists($this->destination . $dir)) {
$this->output->writeln(' directory: <red>' . $dir . '</red> does not exist!');
$success = false;
}
}
foreach ($this->mappings as $target => $link) {
if (!file_exists($this->destination . $target)) {
$this->output->writeln(' mappings: <red>' . $target . '</red> does not exist!');
$success = false;
}
}
if (!$success) {
$this->output->writeln('');
$this->output->writeln('<comment>install should be run with --symlink|--s to symlink first</comment>');
exit;
}
}
}

View File

@@ -0,0 +1,113 @@
<?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\Cli;
use Grav\Common\Grav;
use Grav\Common\Security;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Style\SymfonyStyle;
class SecurityCommand extends ConsoleCommand
{
/** @var ProgressBar $progress */
protected $progress;
/**
*
*/
protected function configure()
{
$this
->setName("security")
->setDescription("Capable of running various Security checks")
->setHelp('The <info>security</info> runs various security checks on your Grav site');
$this->source = getcwd();
}
/**
* @return int|null|void
*/
protected function serve()
{
/** @var Grav $grav */
$grav = Grav::instance();
$grav['uri']->init();
$grav['config']->init();
$grav['debugger']->enabled(false);
$grav['streams'];
$grav['plugins']->init();
$grav['themes']->init();
$grav['twig']->init();
$grav['pages']->init();
$this->progress = new ProgressBar($this->output, (count($grav['pages']->routes()) - 1));
$this->progress->setFormat('Scanning <cyan>%current%</cyan> pages [<green>%bar%</green>] <white>%percent:3s%%</white> %elapsed:6s%');
$this->progress->setBarWidth(100);
$io = new SymfonyStyle($this->input, $this->output);
$io->title('Grav Security Check');
$output = Security::detectXssFromPages($grav['pages'], [$this, 'outputProgress']);
$io->newline(2);
if (!empty($output)) {
$counter = 1;
foreach ($output as $route => $results) {
$results_parts = array_map(function($value, $key) {
return $key.': \''.$value . '\'';
}, array_values($results), array_keys($results));
$io->writeln($counter++ .' - <cyan>' . $route . '</cyan> → <red>' . implode(', ', $results_parts) . '</red>');
}
$io->error('Security Scan complete: ' . count($output) . ' potential XSS issues found...');
} else {
$io->success('Security Scan complete: No issues found...');
}
$io->newline(1);
}
/**
* @param $args
*/
public function outputProgress($args)
{
switch ($args['type']) {
case 'count':
$steps = $args['steps'];
$freq = intval($steps > 100 ? round($steps / 100) : $steps);
$this->progress->setMaxSteps($steps);
$this->progress->setRedrawFrequency($freq);
break;
case 'progress':
if (isset($args['complete']) && $args['complete']) {
$this->progress->finish();
} else {
$this->progress->advance();
}
break;
}
}
}

View File

@@ -0,0 +1,47 @@
<?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;
use Grav\Common\Grav;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ConsoleCommand extends Command
{
use ConsoleTrait;
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$this->serve();
}
/**
*
*/
protected function serve()
{
}
protected function displayGPMRelease()
{
$this->output->writeln('');
$this->output->writeln('GPM Releases Configuration: <yellow>' . ucfirst(Grav::instance()['config']->get('system.gpm.releases')) . '</yellow>');
$this->output->writeln('');
}
}

View File

@@ -0,0 +1,134 @@
<?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;
use Grav\Common\Grav;
use Grav\Common\Composer;
use Grav\Common\GravTrait;
use Grav\Console\Cli\ClearCacheCommand;
use RocketTheme\Toolbox\File\YamlFile;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
trait ConsoleTrait
{
use GravTrait;
/**
* @var
*/
protected $argv;
/* @var InputInterface $output */
protected $input;
/* @var OutputInterface $output */
protected $output;
/**
* Set colors style definition for the formatter.
*
* @param InputInterface $input
* @param OutputInterface $output
*/
public function setupConsole(InputInterface $input, OutputInterface $output)
{
// Initialize cache with CLI compatibility
Grav::instance()['config']->set('system.cache.cli_compatibility', true);
Grav::instance()['cache'];
$this->argv = $_SERVER['argv'][0];
$this->input = $input;
$this->output = $output;
$this->output->getFormatter()->setStyle('normal', new OutputFormatterStyle('white'));
$this->output->getFormatter()->setStyle('yellow', new OutputFormatterStyle('yellow', null, array('bold')));
$this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold')));
$this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan', null, array('bold')));
$this->output->getFormatter()->setStyle('green', new OutputFormatterStyle('green', null, array('bold')));
$this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta', null, array('bold')));
$this->output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
}
/**
* @param $path
*/
public function isGravInstance($path)
{
if (!file_exists($path)) {
$this->output->writeln('');
$this->output->writeln("<red>ERROR</red>: Destination doesn't exist:");
$this->output->writeln(" <white>$path</white>");
$this->output->writeln('');
exit;
}
if (!is_dir($path)) {
$this->output->writeln('');
$this->output->writeln("<red>ERROR</red>: Destination chosen to install is not a directory:");
$this->output->writeln(" <white>$path</white>");
$this->output->writeln('');
exit;
}
if (!file_exists($path . DS . 'index.php') || !file_exists($path . DS . '.dependencies') || !file_exists($path . DS . 'system' . DS . 'config' . DS . 'system.yaml')) {
$this->output->writeln('');
$this->output->writeln("<red>ERROR</red>: Destination chosen to install does not appear to be a Grav instance:");
$this->output->writeln(" <white>$path</white>");
$this->output->writeln('');
exit;
}
}
public function composerUpdate($path, $action = 'install')
{
$composer = Composer::getComposerExecutor();
return system($composer . ' --working-dir="'.$path.'" --no-interaction --no-dev --prefer-dist -o '. $action);
}
/**
* @param array $all
*
* @return int
* @throws \Exception
*/
public function clearCache($all = [])
{
if ($all) {
$all = ['--all' => true];
}
$command = new ClearCacheCommand();
$input = new ArrayInput($all);
return $command->run($input, $this->output);
}
/**
* Load the local config file
*
* @return mixed string the local config file name. false if local config does not exist
*/
public function loadLocalConfig()
{
$home_folder = getenv('HOME') ?: getenv('HOMEDRIVE') . getenv('HOMEPATH');
$local_config_file = $home_folder . '/.grav/config';
if (file_exists($local_config_file)) {
$file = YamlFile::instance($local_config_file);
$this->local_config = $file->content();
$file->free();
return $local_config_file;
}
return false;
}
}

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');
}
}
}
}

View File

@@ -0,0 +1,29 @@
<?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\TerminalObjects;
class Table extends \League\CLImate\TerminalObject\Basic\Table
{
public function result()
{
$this->column_widths = $this->getColumnWidths();
$this->table_width = $this->getWidth();
$this->border = $this->getBorder();
$this->buildHeaderRow();
foreach ($this->data as $key => $columns) {
$this->rows[] = $this->buildRow($columns);
}
$this->rows[] = $this->border;
return $this->rows;
}
}