diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d098dcd8..a980a9221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# v1.7.2 +## 01/21/2021 + +1. [](#improved) + * IMPORTANT - Please [checkout the process](https://getgrav.org/blog/grav-170-cli-self-upgrade-bug) to `self-upgrade` from CLI if you are on **Grav 1.7.0 or 1.7.1** + * Added support for symlinking individual plugins and themes by using `bin/grav install -p myplugin` or `-t mytheme` + * Added support for symlinking plugins and themes with `hebe.json` file to support custom folder structures + * Added support for running post-install scripts in `bin/gpm selfupgrade` if Grav was updated manually +1. [](#bugfix) + * Fixed default GPM Channel back to 'stable' - this was inadvertently left as 'testing' [#3163](https://github.com/getgrav/grav/issues/3163) + * Fixed broken stream initialization if `environment://` paths aren't streams + * Fixed Clockwork debugger in sub-folder multi-site setups + * Fixed `Unsupported option "curl" passed to "Symfony\Component\HttpClient\CurlHttpClient"` in `bin/gpm selfupgrade` [#3165](https://github.com/getgrav/grav/issues/3165) + # v1.7.1 ## 01/20/2021 diff --git a/system/config/system.yaml b/system/config/system.yaml index 9925b76bf..46398e1a0 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -173,7 +173,7 @@ session: path: gpm: - releases: testing # Set to either 'stable' or 'testing' + releases: stable # Set to either 'stable' or 'testing' proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128) method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help. diff --git a/system/defines.php b/system/defines.php index 471b61f6f..5b20589d1 100644 --- a/system/defines.php +++ b/system/defines.php @@ -8,8 +8,8 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.7.1'); -define('GRAV_SCHEMA', '1.7.0_2020-11-20_1'); +define('GRAV_VERSION', '1.7.2'); +define('GRAV_SCHEMA', '1.7.2_2021-01-21_1'); define('GRAV_TESTING', false); // PHP minimum requirement diff --git a/system/src/Grav/Common/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php index cd5d845f9..96c9683dd 100644 --- a/system/src/Grav/Common/Config/Setup.php +++ b/system/src/Grav/Common/Config/Setup.php @@ -362,19 +362,27 @@ class Setup extends Data try { // If environment is found, remove all missing override locations (B/C compatibility). if ($locator->findResource('environment://', true)) { - $prefixes = $this->get('streams.schemes.environment.prefixes.'); - $update = false; - foreach ($prefixes as $i => $prefix) { - if ($locator->findResource($prefix, true)) { - break; + $force = $this->get('streams.schemes.environment.force', false); + if (!$force) { + $prefixes = $this->get('streams.schemes.environment.prefixes.'); + $update = false; + foreach ($prefixes as $i => $prefix) { + if ($locator->isStream($prefix)) { + if ($locator->findResource($prefix, true)) { + break; + } + } elseif (file_exists($prefix)) { + break; + } + + unset($prefixes[$i]); + $update = true; } - unset($prefixes[$i]); - $update = true; - } - if ($update) { - $this->set('streams.schemes.environment.prefixes', ['' => array_values($prefixes)]); - $this->initializeLocator($locator); + if ($update) { + $this->set('streams.schemes.environment.prefixes', ['' => array_values($prefixes)]); + $this->initializeLocator($locator); + } } } diff --git a/system/src/Grav/Common/Debugger.php b/system/src/Grav/Common/Debugger.php index b90f9743c..8e1db0a48 100644 --- a/system/src/Grav/Common/Debugger.php +++ b/system/src/Grav/Common/Debugger.php @@ -279,7 +279,8 @@ class Debugger ->withHeader('X-Clockwork-Id', $clockworkRequest->id) ->withHeader('X-Clockwork-Version', $clockwork::VERSION); - $basePath = Grav::instance()['uri']->rootUrl(); + $grav = Grav::instance(); + $basePath = $this->grav['base_url_relative'] . $grav['pages']->base(); if ($basePath) { $response = $response->withHeader('X-Clockwork-Path', $basePath . '/__clockwork/'); } diff --git a/system/src/Grav/Console/Cli/InstallCommand.php b/system/src/Grav/Console/Cli/InstallCommand.php index 001b83a42..b75a47fcd 100644 --- a/system/src/Grav/Console/Cli/InstallCommand.php +++ b/system/src/Grav/Console/Cli/InstallCommand.php @@ -10,9 +10,12 @@ namespace Grav\Console\Cli; use Grav\Console\GravCommand; +use Grav\Framework\File\Formatter\JsonFormatter; +use Grav\Framework\File\JsonFile; use RocketTheme\Toolbox\File\YamlFile; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; +use function is_array; /** * Class InstallCommand @@ -40,6 +43,18 @@ class InstallCommand extends GravCommand InputOption::VALUE_NONE, 'Symlink the required bits' ) + ->addOption( + 'plugin', + 'p', + InputOption::VALUE_REQUIRED, + 'Install plugin (symlink)' + ) + ->addOption( + 'theme', + 't', + InputOption::VALUE_REQUIRED, + 'Install theme (symlink)' + ) ->addArgument( 'destination', InputArgument::OPTIONAL, @@ -58,7 +73,7 @@ class InstallCommand extends GravCommand $io = $this->getIO(); $dependencies_file = '.dependencies'; - $this->destination = $input->getArgument('destination') ?: ROOT_DIR; + $this->destination = $input->getArgument('destination') ?: GRAV_ROOT; // fix trailing slash $this->destination = rtrim($this->destination, DS) . DS; @@ -93,14 +108,21 @@ class InstallCommand extends GravCommand return 1; } - if (!$input->getOption('symlink')) { + $plugin = $input->getOption('plugin'); + $theme = $input->getOption('theme'); + $name = $plugin ?? $theme; + $symlink = $name || $input->getOption('symlink'); + + if (!$symlink) { // Updates composer first $io->writeln("\nInstalling vendor dependencies"); $io->writeln($this->composerUpdate(GRAV_ROOT, 'install')); $error = $this->gitclone(); } else { - $error = $this->symlink(); + $type = $name ? ($plugin ? 'plugin' : 'theme') : null; + + $error = $this->symlink($name, $type); } return $error; @@ -147,9 +169,11 @@ class InstallCommand extends GravCommand /** * Symlinks * + * @param string|null $name + * @param string|null $type * @return int */ - private function symlink(): int + private function symlink(string $name = null, string $type = null): int { $io = $this->getIO(); @@ -167,7 +191,21 @@ class InstallCommand extends GravCommand $error = 0; $this->destination = rtrim($this->destination, DS); - foreach ($this->config['links'] as $name => $data) { + + if ($name) { + $src = "grav-{$type}-{$name}"; + $links = [ + $name => [ + 'scm' => 'github', // TODO: make configurable + 'src' => $src, + 'path' => "user/{$type}s/{$name}" + ] + ]; + } else { + $links = $this->config['links']; + } + + foreach ($links as $name => $data) { $scm = $data['scm'] ?? null; $src = $data['src'] ?? null; $path = $data['path'] ?? null; @@ -191,7 +229,7 @@ class InstallCommand extends GravCommand } } - if (is_link($to) && !is_file("{$to}/{$name}.yaml")) { + if (is_link($to) && !realpath($to)) { $io->writeln('Removed broken symlink '. $path .''); unlink($to); } @@ -200,8 +238,7 @@ class InstallCommand extends GravCommand $io->newLine(); $error = 1; } elseif (!file_exists($to)) { - symlink($from, $to); - $io->writeln('SUCCESS symlinked ' . $src . ' -> ' . $path . ''); + $error = $this->addSymlinks($from, $to, ['name' => $name, 'src' => $src, 'path' => $path]); $io->newLine(); } else { $io->writeln('destination: ' . $path . ' already exists, skipping...'); @@ -211,4 +248,55 @@ class InstallCommand extends GravCommand return $error; } + + private function addSymlinks(string $from, string $to, array $options): int + { + $io = $this->getIO(); + + $hebe = $this->readHebe($from); + if (null === $hebe) { + symlink($from, $to); + + $io->writeln('SUCCESS symlinked ' . $options['src'] . ' -> ' . $options['path'] . ''); + } else { + $to = GRAV_ROOT; + $name = $options['name']; + $io->writeln("Processing {$name}"); + foreach ($hebe as $section => $symlinks) { + foreach ($symlinks as $symlink) { + $src = trim($symlink['source'], '/'); + $dst = trim($symlink['destination'], '/'); + $s = "{$from}/{$src}"; + $d = "{$to}/{$dst}"; + + if (is_link($d) && !realpath($d)) { + unlink($d); + $io->writeln(' Removed broken symlink '. $dst .''); + } + if (!file_exists($d)) { + symlink($s, $d); + $io->writeln(' symlinked ' . $src . ' -> ' . $dst . ''); + } + } + } + $io->writeln('SUCCESS'); + } + + return 0; + } + + private function readHebe(string $folder): ?array + { + $filename = "{$folder}/hebe.json"; + if (!is_file($filename)) { + return null; + } + + $formatter = new JsonFormatter(); + $file = new JsonFile($filename, $formatter); + $hebe = $file->load(); + $paths = $hebe['platforms']['grav']['nodes'] ?? null; + + return is_array($paths) ? $paths : null; + } } diff --git a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php index bc4f36741..ab9fec713 100644 --- a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php +++ b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php @@ -16,6 +16,7 @@ use Grav\Common\GPM\Response; use Grav\Common\GPM\Upgrader; use Grav\Common\Grav; use Grav\Console\GpmCommand; +use Grav\Installer\Install; use RuntimeException; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputOption; @@ -126,7 +127,32 @@ class SelfupgradeCommand extends GpmCommand } if (!$this->overwrite && !$this->upgrader->isUpgradable()) { - $io->writeln("You are already running the latest version of Grav (v{$local}) released on {$release}"); + $io->writeln("You are already running the latest version of Grav v{$local}"); + $io->writeln("which was released on {$release}"); + + $config = Grav::instance()['config']; + if ($config->get('versions.core.grav.schema') !== GRAV_SCHEMA) { + $io->newLine(); + $io->writeln('However post-install scripts have not been run.'); + if (!$this->all_yes) { + $question = new ConfirmationQuestion( + 'Would you like to run the scripts? [Y|n] ', + true + ); + $answer = $io->askQuestion($question); + } else { + $answer = true; + } + + if ($answer) { + // Finalize installation. + Install::instance()->finalize(); + + $io->write(' |- Running post-install scripts... '); + $io->writeln(" '- Success! "); + $io->newLine(); + } + } return 0; } @@ -220,12 +246,7 @@ class SelfupgradeCommand extends GpmCommand $tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true); $this->tmp = $tmp_dir . '/grav-update-' . uniqid('', false); $options = [ - 'curl' => [ - CURLOPT_TIMEOUT => $this->timeout, - ], - 'fopen' => [ - 'timeout' => $this->timeout, - ], + 'timeout' => $this->timeout, ]; $output = Response::get($package['download'], $options, [$this, 'progress']); diff --git a/system/src/Grav/Installer/Install.php b/system/src/Grav/Installer/Install.php index 5183b1b38..56696b218 100644 --- a/system/src/Grav/Installer/Install.php +++ b/system/src/Grav/Installer/Install.php @@ -283,6 +283,13 @@ ERR; */ public function finalize(): void { + // Finalize can be run without installing Grav first. + if (!$this->updater) { + $versions = Versions::instance(GRAV_ROOT . '/user/config/versions.yaml'); + $this->updater = new VersionUpdater('core/grav', __DIR__ . '/updates', GRAV_VERSION, $versions); + $this->updater->install(); + } + $this->updater->postflight(); Cache::clearCache('all'); diff --git a/system/src/Grav/Installer/YamlUpdater.php b/system/src/Grav/Installer/YamlUpdater.php index 77e6fe780..0747854d2 100644 --- a/system/src/Grav/Installer/YamlUpdater.php +++ b/system/src/Grav/Installer/YamlUpdater.php @@ -148,6 +148,26 @@ final class YamlUpdater array_splice($this->lines, abs($currentLine)+1, 0, $value); } + public function undefine(string $variable): void + { + // If variable does not have value, we're good. + if ($this->get($variable) === null) { + return; + } + + // If one of the parents isn't array, we're good, too. + if (!$this->canDefine($variable)) { + return; + } + + $this->undef($variable); + if (!$this->isHandWritten()) { + return; + } + + // TODO: support also removing property from handwritten configuration file. + } + private function __construct(string $filename) { $content = is_file($filename) ? (string)file_get_contents($filename) : ''; diff --git a/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php b/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php index 554bfd8b1..61206652b 100644 --- a/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php +++ b/system/src/Grav/Installer/updates/1.7.0_2020-11-20_1.php @@ -5,7 +5,8 @@ use Grav\Installer\VersionUpdate; use Grav\Installer\YamlUpdater; return [ - 'preflight' => + 'preflight' => null, + 'postflight' => function () { /** @var VersionUpdate $this */ try { @@ -19,6 +20,5 @@ return [ } catch (\Exception $e) { throw new InstallException('Could not update system configuration to maintain backwards compatibility', $e); } - }, - 'postflight' => null + } ]; diff --git a/system/src/Grav/Installer/updates/1.7.2_2021-01-21_1.php b/system/src/Grav/Installer/updates/1.7.2_2021-01-21_1.php new file mode 100644 index 000000000..d10e1bec2 --- /dev/null +++ b/system/src/Grav/Installer/updates/1.7.2_2021-01-21_1.php @@ -0,0 +1,26 @@ + null, + 'postflight' => + function () { + // Only reset GPM releases value if upgrading from Grav 1.7 RC. + if (version_compare(GRAV_VERSION, '1.7', '<')) { + return; + } + + /** @var VersionUpdate $this */ + try { + // Keep old defaults for backwards compatibility. + $yaml = YamlUpdater::instance(GRAV_ROOT . '/user/config/system.yaml'); + $yaml->undefine('gpm.releases'); + $yaml->save(); + } catch (\Exception $e) { + throw new InstallException('Could not update system configuration to maintain backwards compatibility', $e); + } + } +];