diff --git a/CHANGELOG.md b/CHANGELOG.md
index da86516bc..18914951e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,7 @@
* Make it possible to use an absolute path when loading a blueprint
* Make serialize methods final in `ContentBlock`, `AbstractFile`, `FormTrait`, `ObjectCollectionTrait` and `ObjectTrait`
* Added support for relative paths in `PageObject::getLevelListing()` [#3110](https://github.com/getgrav/grav/issues/3110)
- * Better `--env` support for `bin/grav` and `bin/gpm` console commands
+ * Better `--env` and `--lang` support for `bin/grav`, `bin/gpm` and `bin/plugin` console commands
1. [](#bugfix)
* Fixed port issue with `system.custom_base_url`
* Hide errors with `exif_read_data` in `ImageFile`
diff --git a/bin/plugin b/bin/plugin
index 0f54cfe1a..52729850d 100755
--- a/bin/plugin
+++ b/bin/plugin
@@ -2,12 +2,8 @@
arguments->add([
- 'environment' => [
- 'prefix' => 'e',
- 'longPrefix' => 'env',
- 'description' => 'Configuration Environment',
- 'defaultValue' => 'localhost'
- ]
-]);
-$climate->arguments->parse();
-
-$environment = $climate->arguments->get('environment');
-
// Bootstrap Grav container.
$grav = Grav::instance(array('loader' => $autoload));
-$grav->setup($environment);
-$grav->initializeCli();
-$app = new Application('Grav Plugins Commands', GRAV_VERSION);
-$pattern = '([A-Z]\w+Command\.php)';
-
-// get arguments and strip the application name
-if (null === $argv) {
- $argv = $_SERVER['argv'];
-}
-
-$bin = array_shift($argv);
-$name = array_shift($argv);
-$argv = array_merge([$bin], $argv);
-
-$input = new ArgvInput($argv);
-
-/** @var \Grav\Common\Data\Data $plugin */
-$plugin = $grav['plugins']->get($name);
-
-$output = new ConsoleOutput();
-$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold')));
-$output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
-
-if (!$name) {
- $output->writeln('');
- $output->writeln('Usage:');
- $output->writeln(" {$bin} [slug] [command] [arguments]");
- $output->writeln('');
- $output->writeln('Example:');
- $output->writeln(" {$bin} error log -l 1 --trace");
- $list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm', 'levels' => 2]);
-
- $total = 0;
- if (count($list)) {
- $available = [];
- $output->writeln('');
- $output->writeln('Plugins with CLI available:');
- foreach ($list as $index => $entry) {
- $split = explode('/', $entry);
- $entry = array_shift($split);
- $index = str_pad($index++ + 1, 2, '0', STR_PAD_LEFT);
- $total = str_pad($total++ + 1, 2, '0', STR_PAD_LEFT);
- if (\in_array($entry, $available, true)) {
- $total--;
- continue;
- }
-
- $available[] = $entry;
- $commands_count = $index - $total + 1;
- $output->writeln(' ' . $total . '. ' . str_pad($entry, 15) . " {$bin} {$entry} list");
- }
- }
-
- exit;
-}
-
-if (null === $plugin) {
- $output->writeln('');
- $output->writeln("$name plugin not found");
- die;
-}
-
-if (!$plugin->enabled) {
- $output->writeln('');
- $output->writeln("$name not enabled");
- die;
-}
-
-
-if ($plugin === null) {
- $output->writeln("Grav Plugin '{$name}' is not installed");
- exit;
-}
-
-$path = 'plugins://' . $name . '/cli';
-
-try {
- $commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm', 'levels' => 1]);
-} catch (\RuntimeException $e) {
- $output->writeln("No Console Commands for '{$name}' where found in '{$path}'");
- exit;
-}
-
-foreach ($commands as $command_path) {
- $full_path = $grav['locator']->findResource("plugins://{$name}/cli/{$command_path}");
- require_once $full_path;
-
- $command_class = 'Grav\Plugin\Console\\' . preg_replace('/.php$/', '', $command_path);
- $command = new $command_class();
- $app->add($command);
-}
-
-$app->run($input);
+$app = new PluginApplication('Grav Plugins Commands', GRAV_VERSION);
+$app->run();
diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php
index 02a189d9b..99a1d70af 100644
--- a/system/src/Grav/Common/Plugins.php
+++ b/system/src/Grav/Common/Plugins.php
@@ -173,7 +173,7 @@ class Plugins extends Iterator
/**
* Return list of all plugin data with their blueprints.
*
- * @return array
+ * @return array
*/
public static function all()
{
diff --git a/system/src/Grav/Console/Application/Application.php b/system/src/Grav/Console/Application/Application.php
new file mode 100644
index 000000000..1634a8447
--- /dev/null
+++ b/system/src/Grav/Console/Application/Application.php
@@ -0,0 +1,82 @@
+environment = $input->getOption('env');
+ $this->language = $input->getOption('lang') ?? $this->language;
+ $this->init();
+
+ return parent::getCommandName($input);
+ }
+
+ protected function init(): void
+ {
+ if ($this->initialized) {
+ return;
+ }
+
+ $this->initialized = true;
+
+ $grav = Grav::instance();
+ $grav->setup($this->environment);
+ }
+
+ /**
+ * Add global a --env option.
+ *
+ * @return InputDefinition
+ */
+ protected function getDefaultInputDefinition(): InputDefinition
+ {
+ $inputDefinition = parent::getDefaultInputDefinition();
+ $inputDefinition->addOption(
+ new InputOption(
+ 'env',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'Use environment configuration (defaults to localhost)'
+ )
+ );
+ $inputDefinition->addOption(
+ new InputOption(
+ 'lang',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'Language to be used (defaults to en)'
+ )
+ );
+
+ return $inputDefinition;
+ }
+}
diff --git a/system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php b/system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php
new file mode 100644
index 000000000..bff1e9812
--- /dev/null
+++ b/system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php
@@ -0,0 +1,95 @@
+commands = [];
+
+ try {
+ $path = "plugins://{$name}/cli";
+ $pattern = '([A-Z]\w+Command\.php)';
+
+ $commands = is_dir($path) ? Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm', 'levels' => 1]) : [];
+ } catch (RuntimeException $e) {
+ throw new RuntimeException("Failed to load console commands for plugin {$name}");
+ }
+
+ $grav = Grav::instance();
+
+ /** @var UniformResourceLocator $locator */
+ $locator = $grav['locator'];
+ foreach ($commands as $command_path) {
+ $full_path = $locator->findResource("plugins://{$name}/cli/{$command_path}");
+ require_once $full_path;
+
+ $command_class = 'Grav\Plugin\Console\\' . preg_replace('/.php$/', '', $command_path);
+ if (class_exists($command_class)) {
+ $command = new $command_class();
+ if ($command instanceof Command) {
+ $this->commands[$command->getName()] = $command;
+ }
+ }
+ }
+ }
+
+ /**
+ * @param string $name
+ * @return Command
+ */
+ public function get($name): Command
+ {
+ $command = $this->commands[$name] ?? null;
+ if (null === $command) {
+ throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
+ }
+
+ return $command;
+ }
+
+ /**
+ * @param string $name
+ * @return bool
+ */
+ public function has($name): bool
+ {
+ return isset($this->commands[$name]);
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getNames(): array
+ {
+ return array_keys($this->commands);
+ }
+}
diff --git a/system/src/Grav/Console/Application/GpmApplication.php b/system/src/Grav/Console/Application/GpmApplication.php
index fbaf468a8..69bb2344f 100644
--- a/system/src/Grav/Console/Application/GpmApplication.php
+++ b/system/src/Grav/Console/Application/GpmApplication.php
@@ -9,7 +9,6 @@
namespace Grav\Console\Application;
-use Codeception\Application;
use Grav\Console\Gpm\DirectInstallCommand;
use Grav\Console\Gpm\IndexCommand;
use Grav\Console\Gpm\InfoCommand;
diff --git a/system/src/Grav/Console/Application/GravApplication.php b/system/src/Grav/Console/Application/GravApplication.php
index 73761e1a6..45c8b13eb 100644
--- a/system/src/Grav/Console/Application/GravApplication.php
+++ b/system/src/Grav/Console/Application/GravApplication.php
@@ -9,7 +9,6 @@
namespace Grav\Console\Application;
-use Codeception\Application;
use Grav\Console\Cli\BackupCommand;
use Grav\Console\Cli\CleanCommand;
use Grav\Console\Cli\ClearCacheCommand;
diff --git a/system/src/Grav/Console/Application/PluginApplication.php b/system/src/Grav/Console/Application/PluginApplication.php
new file mode 100644
index 000000000..aca1c4e11
--- /dev/null
+++ b/system/src/Grav/Console/Application/PluginApplication.php
@@ -0,0 +1,99 @@
+addCommands([
+ new PluginListCommand(),
+ ]);
+ }
+
+ public function setPluginName(string $pluginName): void
+ {
+ $this->pluginName = $pluginName;
+ }
+
+ public function getPluginName(): string
+ {
+ return $this->pluginName;
+ }
+
+ /**
+ * @param InputInterface|null $input
+ * @param OutputInterface|null $output
+ * @return int
+ * @throws Throwable
+ */
+ public function run(InputInterface $input = null, OutputInterface $output = null): int
+ {
+ if (null === $input) {
+ $argv = $_SERVER['argv'] ?? [];
+
+ $bin = array_shift($argv);
+ $this->pluginName = array_shift($argv);
+ $argv = array_merge([$bin], $argv);
+
+ $input = new ArgvInput($argv);
+ }
+
+ return parent::run($input, $output);
+ }
+
+ protected function init(): void
+ {
+ if ($this->initialized) {
+ return;
+ }
+
+ parent::init();
+
+ if (null === $this->pluginName) {
+ $this->setDefaultCommand('plugins:list');
+
+ return;
+ }
+
+ $grav = Grav::instance();
+ $grav->initializeCli();
+
+ /** @var Data $plugin */
+ $plugin = $this->pluginName ? $grav['plugins']->get($this->pluginName) : null;
+ if (null === $plugin) {
+ throw new NamespaceNotFoundException("Plugin \"{$this->pluginName}\" is not installed.");
+ }
+ if (!$plugin->enabled) {
+ throw new NamespaceNotFoundException("Plugin \"{$this->pluginName}\" is not enabled.");
+ }
+
+ $this->setCommandLoader(new PluginCommandLoader($this->pluginName));
+ }
+}
diff --git a/system/src/Grav/Console/ConsoleCommand.php b/system/src/Grav/Console/ConsoleCommand.php
index a989b5efc..479c6909d 100644
--- a/system/src/Grav/Console/ConsoleCommand.php
+++ b/system/src/Grav/Console/ConsoleCommand.php
@@ -10,7 +10,6 @@
namespace Grav\Console;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -22,19 +21,6 @@ class ConsoleCommand extends Command
{
use ConsoleTrait;
- /**
- * @param string|null $name The name of the command; passing null means it must be set in configure()
- *
- * @throws LogicException When the command name is empty
- */
- public function __construct(string $name = null)
- {
- parent::__construct($name);
-
- // Add --env option.
- $this->addEnvOption();
- }
-
/**
* @param InputInterface $input
* @param OutputInterface $output
diff --git a/system/src/Grav/Console/ConsoleTrait.php b/system/src/Grav/Console/ConsoleTrait.php
index 150883234..d3f3d30ae 100644
--- a/system/src/Grav/Console/ConsoleTrait.php
+++ b/system/src/Grav/Console/ConsoleTrait.php
@@ -72,72 +72,21 @@ trait ConsoleTrait
$this->setupGrav();
}
- /**
- * @return $this
- */
- final protected function addEnvOption()
- {
- try {
- return $this->addOption(
- 'env',
- 'e',
- InputOption::VALUE_OPTIONAL,
- 'Optional environment to trigger a specific configuration.'
- );
- } catch (LogicException $e) {
- return $this;
- }
- }
-
- /**
- * @return $this
- */
- final protected function addLanguageOption()
- {
- try {
- return $this->addOption(
- 'language',
- 'l',
- InputOption::VALUE_OPTIONAL,
- 'Optional language to be used (multi-language sites only).'
- );
- } catch (LogicException $e) {
- return $this;
- }
- }
-
final protected function setupGrav(): void
{
try {
- $environment = $this->input->getOption('env');
- } catch (InvalidArgumentException $e) {
- $environment = null;
- }
- try {
- $language = $this->input->getOption('language');
- } catch (InvalidArgumentException $e) {
- $language = null;
- }
-
- $grav = Grav::instance();
- if (!$grav->isSetup()) {
- // Set environment.
- $grav->setup($environment);
- } else {
- $this->output->writeln('');
- $this->output->writeln('WARNING: Grav environment already set, please update logic in your CLI command');
- }
-
- if ($language) {
- // Set used language.
- $this->setLanguage($language);
- }
+ $language = $this->input->getOption('lang');
+ if ($language) {
+ // Set used language.
+ $this->setLanguage($language);
+ }
+ } catch (InvalidArgumentException $e) {}
// Initialize cache with CLI compatibility
+ $grav = Grav::instance();
$grav['config']->set('system.cache.cli_compatibility', true);
}
-
/**
* Initialize Grav.
*
diff --git a/system/src/Grav/Console/GpmCommand.php b/system/src/Grav/Console/GpmCommand.php
index 8ca1a7f4d..4e15ebda4 100644
--- a/system/src/Grav/Console/GpmCommand.php
+++ b/system/src/Grav/Console/GpmCommand.php
@@ -12,7 +12,6 @@ namespace Grav\Console;
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -24,19 +23,6 @@ class GpmCommand extends Command
{
use ConsoleTrait;
- /**
- * @param string|null $name The name of the command; passing null means it must be set in configure()
- *
- * @throws LogicException When the command name is empty
- */
- public function __construct(string $name = null)
- {
- parent::__construct($name);
-
- // Add --env option.
- $this->addEnvOption();
- }
-
/**
* @param InputInterface $input
* @param OutputInterface $output
diff --git a/system/src/Grav/Console/GravCommand.php b/system/src/Grav/Console/GravCommand.php
index 080bbddf9..bd1ef03ae 100644
--- a/system/src/Grav/Console/GravCommand.php
+++ b/system/src/Grav/Console/GravCommand.php
@@ -10,7 +10,6 @@
namespace Grav\Console;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -22,19 +21,6 @@ class GravCommand extends Command
{
use ConsoleTrait;
- /**
- * @param string|null $name The name of the command; passing null means it must be set in configure()
- *
- * @throws LogicException When the command name is empty
- */
- public function __construct(string $name = null)
- {
- parent::__construct($name);
-
- // Add --env option.
- $this->addEnvOption();
- }
-
/**
* @param InputInterface $input
* @param OutputInterface $output
diff --git a/system/src/Grav/Console/Plugin/PluginListCommand.php b/system/src/Grav/Console/Plugin/PluginListCommand.php
new file mode 100644
index 000000000..2e301290f
--- /dev/null
+++ b/system/src/Grav/Console/Plugin/PluginListCommand.php
@@ -0,0 +1,70 @@
+setHidden(true);
+ }
+
+ /**
+ * @return int
+ */
+ protected function serve(): int
+ {
+ $bin = 'bin/plugin';
+ $pattern = '([A-Z]\w+Command\.php)';
+
+ $output = $this->output;
+ $output->writeln('');
+ $output->writeln('Usage:');
+ $output->writeln(" {$bin} [slug] [command] [arguments]");
+ $output->writeln('');
+ $output->writeln('Example:');
+ $output->writeln(" {$bin} error log -l 1 --trace");
+
+ $output->writeln('');
+ $output->writeln('Plugins with CLI available:');
+
+ $plugins = Plugins::all();
+ $total = 0;
+ foreach ($plugins as $name => $plugin) {
+ if (!$plugin->enabled) {
+ continue;
+ }
+
+ $list = Folder::all("plugins://{$name}", ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm', 'levels' => 1]);
+ if (!$list) {
+ continue;
+ }
+
+ $total++;
+ $index = str_pad($total, 2, '0', STR_PAD_LEFT);
+ $output->writeln(' ' . $index . '. ' . str_pad($name, 15) . " {$bin} {$name} list");
+ }
+
+ return 0;
+ }
+}