Merge branch 'develop' of https://github.com/getgrav/grav into feature/blueprints-update

This commit is contained in:
Matias Griese
2016-03-01 14:56:45 +02:00
19 changed files with 865 additions and 153 deletions

View File

@@ -2,7 +2,7 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.0.10');
define('GRAV_VERSION', '1.1.0-beta');
define('DS', '/');
define('GRAV_PHP_MIN', '5.5.9');

View File

@@ -3,6 +3,17 @@ namespace Grav\Common\GPM\Common;
use Grav\Common\Data\Data;
/**
* @property string name
* @property string version
* @property string available
* @property string package_type
* @property string description_plain
* @property string slug
* @property array author
* @property mixed changelog
*/
class Package {
protected $data;

View File

@@ -194,6 +194,28 @@ class GPM extends Iterator
return $items;
}
/**
* Get the latest release of a package from the GPM
*
* @param $package_name
*
* @return string
*/
public function getLatestVersionOfPackage($package_name)
{
$repository = $this->repository['plugins'];
if (isset($repository[$package_name])) {
return $repository[$package_name]->version;
}
//Not a plugin, it's a theme?
$repository = $this->repository['themes'];
if (isset($repository[$package_name])) {
return $repository[$package_name]->version;
}
}
/**
* Check if a Plugin or Theme is updatable
* @param string $slug The slug of the package
@@ -349,11 +371,6 @@ class GPM extends Iterator
return false;
}
/**
* Returns the list of Plugins and Themes available in the repository
* @return array Array of available Plugins and Themes
* Format: ['plugins' => array, 'themes' => array]
*/
/**
* Searches for a list of Packages in the repository
* @param array $searches An array of either slugs or names

View File

@@ -12,7 +12,7 @@ class Plugins extends AbstractPackageCollection
*/
protected $type = 'plugins';
protected $repository = 'https://getgrav.org/downloads/plugins.json';
protected $repository = 'https://getgrav.org/downloads/plugins.json?v=' . GRAV_VERSION;
/**
* Local Plugins Constructor

View File

@@ -12,7 +12,7 @@ class Themes extends AbstractPackageCollection
*/
protected $type = 'themes';
protected $repository = 'https://getgrav.org/downloads/themes.json';
protected $repository = 'https://getgrav.org/downloads/themes.json?v=' . GRAV_VERSION;
/**
* Local Themes Constructor

View File

@@ -14,7 +14,10 @@ use Symfony\Component\Console\Input\InputArgument;
*/
class BackupCommand extends ConsoleCommand
{
/** @var string $source */
protected $source;
/** @var ProgressBar $progress */
protected $progress;
/**

View File

@@ -4,7 +4,6 @@ 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\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
@@ -184,6 +183,9 @@ class CleanCommand extends Command
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
@@ -214,7 +216,7 @@ class CleanCommand extends Command
}
}
/**
/**
* Set colors style definition for the formatter.
*
* @param InputInterface $input
@@ -222,16 +224,16 @@ class CleanCommand extends Command
*/
public function setupConsole(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$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')));
$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

@@ -2,7 +2,6 @@
namespace Grav\Console\Cli;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
/**

View File

@@ -43,17 +43,17 @@ class NewProjectCommand extends ConsoleCommand
$sandboxCommand = $this->getApplication()->find('sandbox');
$installCommand = $this->getApplication()->find('install');
$sandboxArguments = new ArrayInput(array(
$sandboxArguments = new ArrayInput([
'command' => 'sandbox',
'destination' => $this->input->getArgument('destination'),
'-s' => $this->input->getOption('symlink')
));
]);
$installArguments = new ArrayInput(array(
$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

@@ -15,7 +15,7 @@ class SandboxCommand extends ConsoleCommand
/**
* @var array
*/
protected $directories = array(
protected $directories = [
'/backup',
'/cache',
'/logs',
@@ -27,22 +27,22 @@ class SandboxCommand extends ConsoleCommand
'/user/data',
'/user/plugins',
'/user/themes',
);
];
/**
* @var array
*/
protected $files = array(
protected $files = [
'/.dependencies',
'/.htaccess',
'/user/config/site.yaml',
'/user/config/system.yaml',
);
];
/**
* @var array
*/
protected $mappings = array(
protected $mappings = [
'/.editorconfig' => '/.editorconfig',
'/.gitignore' => '/.gitignore',
'/CHANGELOG.md' => '/CHANGELOG.md',
@@ -56,7 +56,7 @@ class SandboxCommand extends ConsoleCommand
'/vendor' => '/vendor',
'/webserver-configs' => '/webserver-configs',
'/codeception.yml' => '/codeception.yml',
);
];
/**
* @var string
@@ -200,7 +200,7 @@ class SandboxCommand extends ConsoleCommand
*/
private function initFiles()
{
$this->check($this->output);
$this->check();
$this->output->writeln('');
$this->output->writeln('<comment>File Initializing</comment>');
@@ -225,8 +225,6 @@ class SandboxCommand extends ConsoleCommand
if (!$files_init) {
$this->output->writeln(' <red>Files already exist</red>');
}
}
/**
@@ -239,7 +237,7 @@ class SandboxCommand extends ConsoleCommand
// get pages files and initialize if no pages exist
$pages_dir = $this->destination . '/user/pages';
$pages_files = array_diff(scandir($pages_dir), array('..', '.'));
$pages_files = array_diff(scandir($pages_dir), ['..', '.']);
if (count($pages_files) == 0) {
$destination = $this->source . '/user/pages';
@@ -269,7 +267,6 @@ class SandboxCommand extends ConsoleCommand
$this->output->writeln("");
}
/**
*
*/
@@ -295,6 +292,7 @@ class SandboxCommand extends ConsoleCommand
$success = false;
}
}
if (!$success) {
$this->output->writeln('');
$this->output->writeln('<comment>install should be run with --symlink|--s to symlink first</comment>');

View File

@@ -3,7 +3,6 @@ namespace Grav\Console;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**

View File

@@ -83,7 +83,7 @@ class InfoCommand extends ConsoleCommand
$this->output->writeln("<green>" . str_pad("Author",
12) . ":</green> " . $foundPackage->author['name'] . ' <' . $foundPackage->author['email'] . '> ' . $packageURL);
foreach (array(
foreach ([
'version',
'keywords',
'date',
@@ -95,7 +95,7 @@ class InfoCommand extends ConsoleCommand
'bugs',
'zipball_url',
'license'
) as $info) {
] as $info) {
if (isset($foundPackage->$info)) {
$name = ucfirst($info);
$data = $foundPackage->$info;

View File

@@ -5,12 +5,12 @@ use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
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;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Yaml\Yaml;
define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/(.*)/');
@@ -21,29 +21,29 @@ define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/
*/
class InstallCommand extends ConsoleCommand
{
/**
* @var
*/
/** @var */
protected $data;
/**
* @var
*/
/** @var GPM */
protected $gpm;
/**
* @var
*/
/** @var */
protected $destination;
/**
* @var
*/
/** @var */
protected $file;
/**
* @var
*/
/** @var */
protected $tmp;
/** @var */
protected $local_config;
/** @var bool */
protected $use_symlinks;
/** @var array */
protected $demo_processing = [];
/**
*
@@ -81,7 +81,17 @@ class InstallCommand extends ConsoleCommand
}
/**
* @return int|null|void
* Allows to set the GPM object, used for testing the class
*
* @param $gpm
*/
public function setGpm($gpm)
{
$this->gpm = $gpm;
}
/**
* @return int|null|void|bool
*/
protected function serve()
{
@@ -91,7 +101,7 @@ class InstallCommand extends ConsoleCommand
$packages = array_map('strtolower', $this->input->getArgument('package'));
$this->data = $this->gpm->findPackages($packages);
if (false === $this->isWindows() && @is_file(getenv("HOME").'/.grav/config')) {
if (false === $this->isWindows() && @is_file(getenv("HOME") . '/.grav/config')) {
$local_config_file = exec('eval echo ~/.grav/config');
if (file_exists($local_config_file)) {
$this->local_config = Yaml::parse($local_config_file);
@@ -122,36 +132,344 @@ class InstallCommand extends ConsoleCommand
unset($this->data['not_found']);
unset($this->data['total']);
foreach ($this->data as $data) {
foreach ($data as $package) {
//Check for dependencies
if (isset($package->dependencies)) {
$this->output->writeln("Package <cyan>" . $package->name . "</cyan> has ". count($package->dependencies) . " required dependencies that must be installed first...");
$this->output->writeln('');
if (isset($this->local_config)) {
// Symlinks available, ask if Grav should use them
$dependency_data = $this->gpm->findPackages($package->dependencies);
$this->use_symlinks = false;
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Should Grav use the symlinks if available? [y|N] ', false);
if (!$dependency_data['total']) {
$this->output->writeln("No dependencies found...");
$this->output->writeln('');
} else {
unset($dependency_data['total']);
if ($helper->ask($this->input, $this->output, $question)) {
$this->use_symlinks = true;
}
}
foreach($dependency_data as $type => $dep_data) {
foreach($dep_data as $name => $dep_package) {
$this->output->writeln('');
$this->processPackage($dep_package);
}
}
}
try {
$dependencies = $this->processDependencies($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) {
//First, check for Grav dependency. If a dependency requires Grav > the current version, abort and tell.
if (isset($dependencies['grav'])) {
if (version_compare($this->calculateVersionNumberFromDependencyVersion($dependencies['grav']), GRAV_VERSION) === 1) {
//Needs a Grav update first
$this->output->writeln("<red>One of the package dependencies requires Grav " . $dependencies['grav'] . ". Please update Grav first with `bin/gpm selfupgrade`</red>");
return false;
}
unset($dependencies['grav']);
}
$this->processPackage($package);
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...");
} catch (\Exception $e) {
$this->output->writeln("<red>Installation aborted</red>");
return false;
}
}
//We're done installing dependencies. Install the actual packages
foreach ($this->data as $data) {
foreach ($data as $packageName => $package) {
if (in_array($packageName, array_keys($dependencies))) {
$this->output->writeln("<green>Package " . $packageName . " already installed as dependency</green>");
} else {
$this->processPackage($package);
}
}
}
if (count($this->demo_processing) > 0) {
foreach ($this->demo_processing as $package) {
$this->installDemoContent($package);
}
}
// clear cache after successful upgrade
$this->clearCache();
return true;
}
/**
* 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
*
* @throws \Exception
*/
public function installDependencies($dependencies, $type, $message) {
$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> requires a newer version");
}
$this->output->writeln("");
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Update these packages? [y|N] ', false);
if ($helper->ask($this->input, $this->output, $question)) {
foreach ($packages as $dependencyName => $dependencyVersion) {
$this->processPackage($dependencyName);
}
$this->output->writeln('');
} else {
throw new \Exception();
}
}
}
/**
* Fetch the dependencies, check the installed packages and return an array with
* the list of packages with associated an information on what to do: install, update or ignore.
*
* `ignore` means the package is already installed and can be safely left as-is.
* `install` means the package is not installed and must be installed.
* `update` means the package is already installed and must be updated as a dependency needs a higher version.
*
* @param array $packages
*
* @return mixed
* @throws \Exception
*/
public function processDependencies($packages) {
$dependencies = $this->calculateMergedDependenciesOfPackages($packages);
foreach ($dependencies as $dependencySlug => $dependencyVersion) {
if ($this->gpm->isPluginInstalled($dependencySlug)) {
$dependencyVersion = $this->calculateVersionNumberFromDependencyVersion($dependencyVersion);
// check the version, if an update is not strictly required mark as 'ignore'
$locator = Grav::instance()['locator'];
$blueprints_path = $locator->findResource('plugins://' . $dependencySlug . DS . 'blueprints.yaml');
$package_yaml = Yaml::parse(file_get_contents($blueprints_path));
$currentlyInstalledVersion = $package_yaml['version'];
//if I already have the latest release, remove the dependency
$latestRelease = $this->gpm->getLatestVersionOfPackage($dependencySlug);
if (version_compare($latestRelease, $dependencyVersion) == -1) {
//throw an exception if a required version cannot be found in the GPM yet
throw new \Exception('Dependency ' . $package_yaml['name'] . ' is required in a version higher than the latest release. Try running `bin/gpm -f index` to force a refresh of the GPM cache', 1);
}
if (version_compare($currentlyInstalledVersion, $dependencyVersion) == -1) {
$dependencies[$dependencySlug] = 'update';
} else {
if ($currentlyInstalledVersion == $latestRelease) {
unset($dependencies[$dependencySlug]);
} else {
$dependencies[$dependencySlug] = 'ignore';
}
}
} else {
$dependencies[$dependencySlug] = 'install';
}
}
return $dependencies;
}
/**
* Calculates and merges the dependencies of a package
*
* @param string $packageName The package information
*
* @param array $dependencies The dependencies array
*
* @return array
* @throws \Exception
*/
private function calculateMergedDependenciesOfPackage($packageName, $dependencies)
{
$packageData = $this->gpm->findPackage($packageName);
//Check for dependencies
if (isset($packageData->dependencies)) {
foreach ($packageData->dependencies as $dependency) {
$current_package_name = $dependency['name'];
if (isset($dependency['version'])) {
$current_package_version_information = $dependency['version'];
}
if (!isset($dependencies[$current_package_name])) {
// Dependency added for the first time
if (!isset($current_package_version_information)) {
$dependencies[$current_package_name] = '*';
} else {
$dependencies[$current_package_name] = $current_package_version_information;
}
//Factor in the package dependencies too
$dependencies = $this->calculateMergedDependenciesOfPackage($current_package_name, $dependencies);
}
else {
// Dependency already added by another package
//if this package requires a version higher than the currently stored one, store this requirement instead
if (isset($current_package_version_information) && $current_package_version_information !== '*') {
$currently_stored_version_information = $dependencies[$current_package_name];
$currently_stored_version_number = $this->calculateVersionNumberFromDependencyVersion($currently_stored_version_information);
$currently_stored_version_is_in_next_significant_release_format = false;
if ($this->versionFormatIsNextSignificantRelease($currently_stored_version_information)) {
$currently_stored_version_is_in_next_significant_release_format = true;
}
if (!$currently_stored_version_number) {
$currently_stored_version_number = '*';
}
$current_package_version_number = $this->calculateVersionNumberFromDependencyVersion($current_package_version_information);
if (!$current_package_version_number) {
throw new \Exception('Bad format for version of dependency ' . $current_package_name . ' for package ' . $packageName, 1);
}
$current_package_version_is_in_next_significant_release_format = false;
if ($this->versionFormatIsNextSignificantRelease($current_package_version_information)) {
$current_package_version_is_in_next_significant_release_format = true;
}
//If I had stored '*', change right away with the more specific version required
if ($currently_stored_version_number === '*') {
$dependencies[$current_package_name] = $current_package_version_information;
} else {
if (!$currently_stored_version_is_in_next_significant_release_format && !$current_package_version_is_in_next_significant_release_format) {
//Comparing versions equals or higher, a simple version_compare is enough
if (version_compare($currently_stored_version_number, $current_package_version_number) == -1) { //Current package version is higher
$dependencies[$current_package_name] = $current_package_version_information;
}
} else {
$compatible = $this->checkNextSignificantReleasesAreCompatible($currently_stored_version_number, $current_package_version_number);
if (!$compatible) {
throw new \Exception('Dependency ' . $current_package_name . ' is required in two incompatible versions', 2);
}
}
}
}
}
}
}
return $dependencies;
}
/**
* Calculates and merges the dependencies of the passed packages
*
* @param array $packages
*
* @return mixed
* @throws \Exception
*/
public function calculateMergedDependenciesOfPackages($packages)
{
$dependencies = [];
foreach ($packages as $package) {
$dependencies = $this->calculateMergedDependenciesOfPackage($package, $dependencies);
}
return $dependencies;
}
/**
* Returns the actual version from a dependency version string.
* Examples:
* $versionInformation == '~2.0' => returns '2.0'
* $versionInformation == '>=2.0.2' => returns '2.0.2'
* $versionInformation == '*' => returns null
* $versionInformation == '' => returns null
*
* @param $versionInformation
*
* @return null|string
*/
public function calculateVersionNumberFromDependencyVersion($versionInformation)
{
if ($this->versionFormatIsNextSignificantRelease($versionInformation)) {
return substr($versionInformation, 1);
} elseif ($this->versionFormatIsEqualOrHigher($versionInformation)) {
return substr($versionInformation, 2);
}
return null;
}
/**
* Check if the passed version information contains next significant release (tilde) operator
*
* Example: returns true for $version: '~2.0'
*
* @param $version
*
* @return bool
*/
public function versionFormatIsNextSignificantRelease($version) {
return substr($version, 0, 1) == '~';
}
/**
* Check if the passed version information contains equal or higher operator
*
* Example: returns true for $version: '>=2.0'
*
* @param $version
*
* @return bool
*/
public function versionFormatIsEqualOrHigher($version) {
return substr($version, 0, 2) == '>=';
}
/**
* Check if two releases are compatible by next significant release
*
* ~1.2 is equivalent to >=1.2 <2.0.0
* ~1.2.3 is equivalent to >=1.2.3 <1.3.0
*
* In short, allows the last digit specified to go up
*
* @param string $version1 the version string (e.g. '2.0.0' or '1.0')
* @param string $version2 the version string (e.g. '2.0.0' or '1.0')
*
* @return bool
*/
public function checkNextSignificantReleasesAreCompatible($version1, $version2)
{
$version1array = explode('.', $version1);
$version2array = explode('.', $version2);
if (count($version1array) > count($version2array)) {
list($version1array, $version2array) = [$version2array, $version1array];
}
$i = 0;
while ($i < count($version1array) - 1) {
if ($version1array[$i] != $version2array[$i]) {
return false;
}
$i++;
}
return true;
}
/**
@@ -159,78 +477,65 @@ class InstallCommand extends ConsoleCommand
*/
private function processPackage($package)
{
$install_options = ['GPM'];
// if no name, not found in GPM
if (!isset($package->version)) {
unset($install_options[0]);
}
// if local config found symlink is a valid option
if (isset($this->local_config) && $this->getSymlinkSource($package)) {
$install_options[] = 'Symlink';
}
// if override set, can install via git
if (isset($package->override_repository)) {
$install_options[] = 'Git';
$symlink = false;
if ($this->use_symlinks) {
if ($this->getSymlinkSource($package) || !isset($package->version)) {
$symlink = true;
}
}
// reindex list
$install_options = array_values($install_options);
$symlink ? $this->processSymlink($package) : $this->processGpm($package);
if (count($install_options) == 0) {
// no valid install options - error and return
$this->output->writeln("<red>not valid installation methods found!</red>");
return;
} elseif (count($install_options) == 1) {
// only one option, use it...
$method = $install_options[0];
} else {
$helper = $this->getHelper('question');
$question = new ChoiceQuestion(
'Please select installation method for <cyan>' . $package->name . '</cyan> (<magenta>'.$install_options[0].' is default</magenta>)', array_values($install_options), 0
);
$question->setErrorMessage('Method %s is invalid');
$method = $helper->ask($this->input, $this->output, $question);
}
$this->output->writeln('');
$method_name = 'process'.$method;
$this->$method_name($package);
$this->installDemoContent($package);
$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';
$dest_dir = $this->destination . DS . 'user';
$pages_dir = $dest_dir . DS . 'pages';
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");
$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);
if (!$helper->ask($this->input, $this->output, $question)) {
$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);
$question = new ConfirmationQuestion('This will backup your current `user/pages` folder to `user/' . $pages_backup . '`, continue? [y|N]', false);
if (!$helper->ask($this->input, $this->output, $question)) {
$this->output->writeln(" '- <red>Skipped!</red> ");
$this->output->writeln('');
return;
}
@@ -259,9 +564,7 @@ class InstallCommand extends ConsoleCommand
*/
private function getGitRegexMatches($package)
{
if (isset($package->override_repository)) {
$repository = $package->override_repository;
} elseif (isset($package->repository)) {
if (isset($package->repository)) {
$repository = $package->repository;
} else {
return false;
@@ -294,6 +597,7 @@ class InstallCommand extends ConsoleCommand
return $from;
}
}
return false;
}
@@ -336,6 +640,7 @@ class InstallCommand extends ConsoleCommand
}
return;
}
@@ -346,34 +651,7 @@ class InstallCommand extends ConsoleCommand
/**
* @param $package
*/
private function processGit($package)
{
$matches = $this->getGitRegexMatches($package);
$this->output->writeln("Preparing to Git clone <cyan>" . $package->name . "</cyan> from " . $matches[0]);
$this->output->write(" |- Checking destination... ");
$checks = $this->checkDestination($package);
if (!$checks) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln('');
} else {
$cmd = 'cd ' . $this->destination . ' && git clone ' . $matches[0] . ' ' . $package->install_path;
exec($cmd);
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Cloning package... <green>ok</green> ");
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
}
/**
* @param $package
*/
private function processGPM($package)
private function processGpm($package)
{
$version = isset($package->available) ? $package->available : $package->version;

View File

@@ -35,7 +35,7 @@ class SelfupgradeCommand extends ConsoleCommand
/**
* @var array
*/
protected $types = array('plugins', 'themes');
protected $types = ['plugins', 'themes'];
/**
* @var
*/

View File

@@ -58,6 +58,38 @@ class UninstallCommand extends ConsoleCommand
->setHelp('The <info>uninstall</info> command allows to uninstall plugins and themes');
}
/**
* Return the list of packages that have the passed one as dependency
*
* @param $package_slug The slug name of the package
*
* @return bool
*/
protected function getPackagesThatDependOnPackage($package_slug)
{
$plugins = $this->gpm->getInstalledPlugins();
$themes = $this->gpm->getInstalledThemes();
$packages = array_merge($plugins->toArray(), $themes->toArray());
$dependent_packages = [];
foreach($packages as $package_name => $package) {
if (isset($package['dependencies'])) {
foreach($package['dependencies'] as $dependency) {
if (is_array($dependency)) {
$dependency = array_keys($dependency)[0];
}
if ($dependency == $package_slug) {
$dependent_packages[] = $package_name;
}
}
}
}
return $dependent_packages;
}
/**
* @return int|null|void
*/
@@ -98,7 +130,6 @@ class UninstallCommand extends ConsoleCommand
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);
@@ -117,6 +148,7 @@ class UninstallCommand extends ConsoleCommand
$this->output->writeln('');
}
}
}
// clear cache after successful upgrade
@@ -132,6 +164,23 @@ class UninstallCommand extends ConsoleCommand
*/
private function uninstallPackage($slug, $package)
{
//check if there are packages that have this as a dependency. Abort and show list
$dependency_packages = $this->getPackagesThatDependOnPackage($slug);
if ($dependency_packages) {
$this->output->writeln('');
$this->output->writeln('');
$this->output->writeln("<red>Uninstallation failed.</red>");
$this->output->writeln('');
if (count($dependency_packages) > 1) {
$this->output->writeln("The installed packages <cyan>" . implode('</cyan>, <cyan>', $dependency_packages) . "</cyan> depend on this package. Please remove those first.");
} else {
$this->output->writeln("The installed package <cyan>" . implode('</cyan>, <cyan>', $dependency_packages) . "</cyan> depend on this package. Please remove it first.");
}
$this->output->writeln('');
exit;
}
$path = Grav::instance()['locator']->findResource($package->package_type . '://' .$slug);
Installer::uninstall($path);
$errorCode = Installer::lastErrorCode();
@@ -149,6 +198,35 @@ class UninstallCommand extends ConsoleCommand
// extra white spaces to clear out the buffer properly
$this->output->writeln(" |- Uninstalling package... <green>ok</green> ");
if (isset($package->dependencies)) {
$questionHelper = $this->getHelper('question');
foreach($package->dependencies as $dependency) {
if (is_array($dependency)) {
$dependency = array_keys($dependency)[0];
}
$dependencyPackage = $this->gpm->findPackage($dependency);
$question = new ConfirmationQuestion(" | '- Delete dependency <cyan>" . $dependency . "</cyan> too? [y|N] ", false);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if ($answer) {
$this->output->writeln(" | '- <red>You decided to delete " . $dependency . ".</red>");
$uninstall = $this->uninstallPackage($dependency, $dependencyPackage);
if (!$uninstall) {
$this->output->writeln(" '- <red>Uninstallation failed or aborted.</red>");
$this->output->writeln('');
} else {
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
}
}
}
return true;
}

View File

@@ -38,7 +38,7 @@ class UpdateCommand extends ConsoleCommand
/**
* @var array
*/
protected $types = array('plugins', 'themes');
protected $types = ['plugins', 'themes'];
/**
* @var GPM $gpm
*/
@@ -149,13 +149,13 @@ class UpdateCommand extends ConsoleCommand
// finally update
$install_command = $this->getApplication()->find('install');
$args = new ArrayInput(array(
$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) {

View File

@@ -70,7 +70,8 @@ class VersionCommand extends ConsoleCommand
}
} else {
if ($installed = $this->gpm->findPackage($package)) {
$installed = $this->gpm->findPackage($package);
if ($installed) {
$name = $installed->name;
$version = $installed->version;

View File

@@ -138,9 +138,9 @@ class InflectorTest extends \Codeception\TestCase\Test
public function testMonthize()
{
$this->assertSame(0, $this->inflector->monthize(10));
$this->assertSame(1, $this->inflector->monthize(30));
$this->assertSame(1, $this->inflector->monthize(33));
$this->assertSame(1, $this->inflector->monthize(41));
$this->assertSame(11, $this->inflector->monthize(365));
$this->assertSame(11, $this->inflector->monthize(364));
}
}

View File

@@ -0,0 +1,326 @@
<?php
use Codeception\Util\Fixtures;
use Grav\Common\Grav;
use Grav\Console\Gpm\InstallCommand;
define('EXCEPTION_BAD_FORMAT', 1);
define('EXCEPTION_INCOMPATIBLE_VERSIONS', 2);
class GpmStub extends stdClass
{
public function findPackage($packageName)
{
if (isset($this->data[$packageName])) {
return $this->data[$packageName];
}
}
public function findPackages()
{
return $this->data;
}
}
/**
* Class InstallCommandTest
*/
class InstallCommandTest extends \Codeception\TestCase\Test
{
/** @var Grav $grav */
protected $grav;
/** @var InstallCommand */
protected $installCommand;
/** @var GpmStub */
protected $gpm;
protected function _before()
{
$this->grav = Fixtures::get('grav');
$this->installCommand = new InstallCommand();
$this->gpm = new GpmStub();
}
protected function _after()
{
}
public function testCalculateMergedDependenciesOfPackages()
{
//////////////////////////////////////////////////////////////////////////////////////////
// First working example
//////////////////////////////////////////////////////////////////////////////////////////
$this->gpm->data = [
'admin' => (object)[
'dependencies' => [
["name" => "grav", "version" => ">=1.0.10"],
["name" => "form", "version" => "~2.0"],
["name" => "login", "version" => ">=2.0"],
["name" => "errors", "version" => "*"],
["name" => "problems"],
]
],
'test' => (object)[
'dependencies' => [
["name" => "errors", "version" => ">=1.0"]
]
],
'grav',
'form' => (object)[
'dependencies' => [
["name" => "errors", "version" => ">=3.2"]
]
]
];
$this->installCommand->setGpm($this->gpm);
$packages = ['admin', 'test'];
$dependencies = $this->installCommand->calculateMergedDependenciesOfPackages($packages);
$this->assertTrue(is_array($dependencies));
$this->assertSame(5, count($dependencies));
$this->assertTrue($dependencies['grav'] == '>=1.0.10');
$this->assertTrue(isset($dependencies['errors']));
$this->assertTrue(isset($dependencies['problems']));
//////////////////////////////////////////////////////////////////////////////////////////
// Second working example
//////////////////////////////////////////////////////////////////////////////////////////
$packages = ['admin', 'form'];
$dependencies = $this->installCommand->calculateMergedDependenciesOfPackages($packages);
$this->assertTrue(is_array($dependencies));
$this->assertSame(5, count($dependencies));
$this->assertTrue($dependencies['errors'] == '>=3.2');
//////////////////////////////////////////////////////////////////////////////////////////
// Third working example
//////////////////////////////////////////////////////////////////////////////////////////
$this->gpm->data = [
'admin' => (object)[
'dependencies' => [
["name" => "errors", "version" => ">=4.0"],
]
],
'test' => (object)[
'dependencies' => [
["name" => "errors", "version" => ">=1.0"]
]
],
'another' => (object)[
'dependencies' => [
["name" => "errors", "version" => ">=3.2"]
]
]
];
$this->installCommand->setGpm($this->gpm);
$packages = ['admin', 'test', 'another'];
$dependencies = $this->installCommand->calculateMergedDependenciesOfPackages($packages);
$this->assertTrue(is_array($dependencies));
$this->assertSame(1, count($dependencies));
$this->assertTrue($dependencies['errors'] == '>=4.0');
//////////////////////////////////////////////////////////////////////////////////////////
// Test alpha / beta / rc
//////////////////////////////////////////////////////////////////////////////////////////
$this->gpm->data = [
'admin' => (object)[
'dependencies' => [
["name" => "package1", "version" => ">=4.0.0-rc1"],
["name" => "package4", "version" => ">=3.2.0"],
]
],
'test' => (object)[
'dependencies' => [
["name" => "package1", "version" => ">=4.0.0-rc2"],
["name" => "package2", "version" => ">=3.2.0-alpha"],
["name" => "package3", "version" => ">=3.2.0-alpha.2"],
["name" => "package4", "version" => ">=3.2.0-alpha"],
]
],
'another' => (object)[
'dependencies' => [
["name" => "package2", "version" => ">=3.2.0-beta.11"],
["name" => "package3", "version" => ">=3.2.0-alpha.1"],
["name" => "package4", "version" => ">=3.2.0-beta"],
]
]
];
$this->installCommand->setGpm($this->gpm);
$packages = ['admin', 'test', 'another'];
$dependencies = $this->installCommand->calculateMergedDependenciesOfPackages($packages);
$this->assertTrue($dependencies['package1'] == '>=4.0.0-rc2');
$this->assertTrue($dependencies['package2'] == '>=3.2.0-beta.11');
$this->assertTrue($dependencies['package3'] == '>=3.2.0-alpha.2');
$this->assertTrue($dependencies['package4'] == '>=3.2.0');
//////////////////////////////////////////////////////////////////////////////////////////
// Raise exception if no version is specified
//////////////////////////////////////////////////////////////////////////////////////////
$this->gpm->data = [
'admin' => (object)[
'dependencies' => [
["name" => "errors", "version" => ">=4.0"],
]
],
'test' => (object)[
'dependencies' => [
["name" => "errors", "version" => ">="]
]
],
];
$this->installCommand->setGpm($this->gpm);
$packages = ['admin', 'test'];
try {
$this->installCommand->calculateMergedDependenciesOfPackages($packages);
$this->fail("Expected Exception not thrown");
} catch (Exception $e) {
$this->assertEquals(EXCEPTION_BAD_FORMAT, $e->getCode());
$this->assertStringStartsWith("Bad format for version of dependency", $e->getMessage());
}
//////////////////////////////////////////////////////////////////////////////////////////
// Raise exception if incompatible versions are specified
//////////////////////////////////////////////////////////////////////////////////////////
$this->gpm->data = [
'admin' => (object)[
'dependencies' => [
["name" => "errors", "version" => "~4.0"],
]
],
'test' => (object)[
'dependencies' => [
["name" => "errors", "version" => "~3.0"]
]
],
];
$this->installCommand->setGpm($this->gpm);
$packages = ['admin', 'test'];
try {
$this->installCommand->calculateMergedDependenciesOfPackages($packages);
$this->fail("Expected Exception not thrown");
} catch (Exception $e) {
$this->assertEquals(EXCEPTION_INCOMPATIBLE_VERSIONS, $e->getCode());
$this->assertStringEndsWith("required in two incompatible versions", $e->getMessage());
}
//////////////////////////////////////////////////////////////////////////////////////////
// Test dependencies of dependencies
//////////////////////////////////////////////////////////////////////////////////////////
$this->gpm->data = [
'admin' => (object)[
'dependencies' => [
["name" => "grav", "version" => ">=1.0.10"],
["name" => "form", "version" => "~2.0"],
["name" => "login", "version" => ">=2.0"],
["name" => "errors", "version" => "*"],
["name" => "problems"],
]
],
'login' => (object)[
'dependencies' => [
["name" => "antimatter", "version" => ">=1.0"]
]
],
'grav',
'antimatter' => (object)[
'dependencies' => [
["name" => "something", "version" => ">=3.2"]
]
]
];
$this->installCommand->setGpm($this->gpm);
$packages = ['admin'];
$dependencies = $this->installCommand->calculateMergedDependenciesOfPackages($packages);
$this->assertTrue(is_array($dependencies));
$this->assertSame(7, count($dependencies));
$this->assertSame('>=1.0.10', $dependencies['grav']);
$this->assertTrue(isset($dependencies['errors']));
$this->assertTrue(isset($dependencies['problems']));
$this->assertTrue(isset($dependencies['antimatter']));
$this->assertTrue(isset($dependencies['something']));
$this->assertSame('>=3.2', $dependencies['something']);
}
public function testVersionFormatIsNextSignificantRelease()
{
$this->assertFalse($this->installCommand->versionFormatIsNextSignificantRelease('>=1.0'));
$this->assertFalse($this->installCommand->versionFormatIsNextSignificantRelease('>=2.3.4'));
$this->assertFalse($this->installCommand->versionFormatIsNextSignificantRelease('>=2.3.x'));
$this->assertFalse($this->installCommand->versionFormatIsNextSignificantRelease('1.0'));
$this->assertTrue($this->installCommand->versionFormatIsNextSignificantRelease('~2.3.x'));
$this->assertTrue($this->installCommand->versionFormatIsNextSignificantRelease('~2.0'));
}
public function testVersionFormatIsEqualOrHigher()
{
$this->assertTrue($this->installCommand->versionFormatIsEqualOrHigher('>=1.0'));
$this->assertTrue($this->installCommand->versionFormatIsEqualOrHigher('>=2.3.4'));
$this->assertTrue($this->installCommand->versionFormatIsEqualOrHigher('>=2.3.x'));
$this->assertFalse($this->installCommand->versionFormatIsEqualOrHigher('~2.3.x'));
$this->assertFalse($this->installCommand->versionFormatIsEqualOrHigher('1.0'));
}
public function testCheckNextSignificantReleasesAreCompatible()
{
/*
* ~1.0 is equivalent to >=1.0 < 2.0.0
* ~1.2 is equivalent to >=1.2 <2.0.0
* ~1.2.3 is equivalent to >=1.2.3 <1.3.0
*/
$this->assertTrue($this->installCommand->checkNextSignificantReleasesAreCompatible('1.0', '1.2'));
$this->assertTrue($this->installCommand->checkNextSignificantReleasesAreCompatible('1.2', '1.0'));
$this->assertTrue($this->installCommand->checkNextSignificantReleasesAreCompatible('1.0', '1.0.10'));
$this->assertTrue($this->installCommand->checkNextSignificantReleasesAreCompatible('1.1', '1.1.10'));
$this->assertTrue($this->installCommand->checkNextSignificantReleasesAreCompatible('30.0', '30.10'));
$this->assertTrue($this->installCommand->checkNextSignificantReleasesAreCompatible('1.0', '1.1.10'));
$this->assertTrue($this->installCommand->checkNextSignificantReleasesAreCompatible('1.0', '1.8'));
$this->assertTrue($this->installCommand->checkNextSignificantReleasesAreCompatible('1.0.1', '1.1'));
$this->assertFalse($this->installCommand->checkNextSignificantReleasesAreCompatible('1.0', '2.2'));
$this->assertFalse($this->installCommand->checkNextSignificantReleasesAreCompatible('0.9.99', '1.0.0'));
$this->assertFalse($this->installCommand->checkNextSignificantReleasesAreCompatible('0.9.99', '1.0.10'));
$this->assertFalse($this->installCommand->checkNextSignificantReleasesAreCompatible('0.9.99', '1.0.10.2'));
}
public function testCalculateVersionNumberFromDependencyVersion()
{
$this->assertSame('2.0', $this->installCommand->calculateVersionNumberFromDependencyVersion('>=2.0'));
$this->assertSame('2.0.2', $this->installCommand->calculateVersionNumberFromDependencyVersion('>=2.0.2'));
$this->assertSame('2.0.2', $this->installCommand->calculateVersionNumberFromDependencyVersion('~2.0.2'));
$this->assertSame('1', $this->installCommand->calculateVersionNumberFromDependencyVersion('~1'));
$this->assertSame(null, $this->installCommand->calculateVersionNumberFromDependencyVersion(''));
$this->assertSame(null, $this->installCommand->calculateVersionNumberFromDependencyVersion('*'));
$this->assertSame(null, $this->installCommand->calculateVersionNumberFromDependencyVersion('2.0.2'));
}
}