Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions .tools/psalm/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1604,9 +1604,6 @@
<code><![CDATA[$currentuser['name']]]></code>
<code><![CDATA[$webuser['name']]]></code>
</PossiblyInvalidArrayAccess>
<PossiblyNullReference>
<code><![CDATA[boot]]></code>
</PossiblyNullReference>
</file>
<file src="src/Console/Command/AbstractCommand.php">
<PossiblyNullArgument>
Expand Down Expand Up @@ -1676,7 +1673,7 @@
</file>
<file src="src/Console/Command/DatabaseDumpSchemaCommand.php">
<ArgumentTypeCoercion>
<code><![CDATA[$input->getArgument('table')]]></code>
<code><![CDATA[$table]]></code>
</ArgumentTypeCoercion>
</file>
<file src="src/Console/Command/DatabaseSetConnectionCommand.php">
Expand Down Expand Up @@ -1770,11 +1767,6 @@
<code><![CDATA[$password]]></code>
</MixedAssignment>
</file>
<file src="src/Console/CommandLoader.php">
<TypeDoesNotContainType>
<code><![CDATA[is_array($commands)]]></code>
</TypeDoesNotContainType>
</file>
<file src="src/Content/Article.php">
<MixedReturnStatement>
<code><![CDATA[Core::getProperty('article_id', 1)]]></code>
Expand Down
4 changes: 2 additions & 2 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,6 @@
new MethodCallRename(Addon\Addon::class, 'getInstalledPackages', 'getInstalledAddons'),
new MethodCallRename(Addon\Addon::class, 'getAvailablePackages', 'getAvailableAddons'),
new MethodCallRename(Addon\Addon::class, 'getSetupPackages', 'getSetupAddons'),
new MethodCallRename(Console\Command\AbstractCommand::class, 'getPackage', 'getAddon'),
new MethodCallRename(Console\Command\AbstractCommand::class, 'setPackage', 'setAddon'),

new MethodCallRename(ApiFunction\Result::class, 'toJSON', 'toJson'),
new MethodCallRename(ApiFunction\Result::class, 'fromJSON', 'fromJson'),
Expand Down Expand Up @@ -524,6 +522,8 @@
new MethodCallToPropertyFetch(ApiFunction\Result::class, 'getMessage', 'message'),
new MethodCallToPropertyFetch(ApiFunction\Result::class, 'requiresReboot', 'requiresReboot'),

new MethodCallToPropertyFetch(Console\Command\AbstractCommand::class, 'getPackage', 'addon'),

new MethodCallToPropertyFetch(Cronjob\Type\AbstractType::class, 'getMessage', 'message'),
new MethodCallToPropertyFetch(Cronjob\Type\AbstractType::class, 'hasMessage', 'message'),
new MethodCallToPropertyFetch(Cronjob\CronjobExecutor::class, 'getMessage', 'message'),
Expand Down
7 changes: 0 additions & 7 deletions schemas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@
"default_config": {
"description": "Default values for Redaxo\\Core\\Config",
"type": "object"
},
"console_commands": {
"description": "Console command names and their corresponding class",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"additionalProperties": true,
Expand Down
8 changes: 7 additions & 1 deletion src/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Redaxo\Core\Filesystem\Path;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
Expand Down Expand Up @@ -68,6 +69,11 @@ public function doRun(InputInterface $input, OutputInterface $output): int
#[Override]
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int
{
// Unwrap the LazyCommand returned by the CommandLoader to get the actual command instance.
if ($command instanceof LazyCommand) {
$command = $command->getCommand();
}

if ($command instanceof AbstractCommand) {
$this->loadPackages($command);
}
Expand Down Expand Up @@ -105,7 +111,7 @@ private function loadPackages(AbstractCommand $command): void

if ('ydeploy:migrate' === $command->getName()) {
// boot only the ydeploy package, which provides the migrate command
$command->getAddon()->boot();
$command->addon->boot();

return;
}
Expand Down
50 changes: 30 additions & 20 deletions src/Console/Command/AbstractCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,25 @@
namespace Redaxo\Core\Console\Command;

use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Exception\LogicException;
use ReflectionObject;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use function sprintf;
use function str_starts_with;

use const DIRECTORY_SEPARATOR;
use const ENT_QUOTES;

abstract class AbstractCommand extends Command
{
protected ?Addon $addon = null;

/** @internal */
public function setAddon(?Addon $addon = null): void
{
$this->addon = $addon;
}

/** @return Addon|null In core commands it returns `null`, otherwise the corresponding addon object */
public function getAddon(): ?Addon
{
return $this->addon;
}

protected function getStyle(InputInterface $input, OutputInterface $output): SymfonyStyle
{
return new SymfonyStyle($input, $output);
/**
* The addon the command belongs to, resolved lazily from the location of the command class.
*
* Only available for addon commands; reading it on a core command throws a {@see LogicException}.
*/
public private(set) Addon $addon {
get => $this->addon ??= $this->resolveAddon();
}

/**
Expand All @@ -45,4 +38,21 @@ protected function decodeMessage(string $message): string

return htmlspecialchars_decode($message, ENT_QUOTES);
}

private function resolveAddon(): Addon
{
$file = new ReflectionObject($this)->getFileName();

if (false !== $file) {
$file = realpath($file) ?: $file;

foreach (Addon::getAvailableAddons() as $addon) {
if (str_starts_with($file, $addon->path . DIRECTORY_SEPARATOR)) {
return $addon;
}
}
}

throw new LogicException(sprintf('Command "%s" does not belong to an addon.', $this::class));
}
}
47 changes: 13 additions & 34 deletions src/Console/Command/AddonActivateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,32 @@

namespace Redaxo\Core\Console\Command;

use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Addon\AddonManager;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
* @internal
*/
class AddonActivateCommand extends AbstractCommand
#[AsCommand(name: 'addon:activate', description: 'Activates the selected addon')]
final class AddonActivateCommand extends AbstractCommand
{
#[Override]
protected function configure(): void
{
$this
->setDescription('Activates the selected addon')
->addArgument('addon-id', InputArgument::REQUIRED, 'The id of the addon, e.g. "yform"', null, static function () {
$packageNames = [];

foreach (Addon::getRegisteredAddons() as $package) {
if ($package->isAvailable()) {
continue;
}

$packageNames[] = $package->name;
}

return $packageNames;
});
}

#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = $this->getStyle($input, $output);

$packageId = $input->getArgument('addon-id');

public function __invoke(
SymfonyStyle $io,
#[Argument('The id of the addon, e.g. "yform"', suggestedValues: static function (): array {
return array_keys(array_filter(Addon::getRegisteredAddons(), static fn (Addon $addon): bool => !$addon->isAvailable()));
})] string $addon,
): int {
// the package manager don't know new packages in the addon folder
// so we need to make them available
AddonManager::synchronizeWithFileSystem();

$package = Addon::get($packageId);
$package = Addon::get($addon);
if (!$package) {
$io->error('Addon "' . $packageId . '" doesn\'t exists!');
$io->error('Addon "' . $addon . '" doesn\'t exists!');
return Command::FAILURE;
}

Expand Down
47 changes: 13 additions & 34 deletions src/Console/Command/AddonDeactivateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,32 @@

namespace Redaxo\Core\Console\Command;

use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Addon\AddonManager;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
* @internal
*/
class AddonDeactivateCommand extends AbstractCommand
#[AsCommand(name: 'addon:deactivate', description: 'Deactivates the selected addon')]
final class AddonDeactivateCommand extends AbstractCommand
{
#[Override]
protected function configure(): void
{
$this
->setDescription('Deactivates the selected addon')
->addArgument('addon-id', InputArgument::REQUIRED, 'The id of the addon, e.g. "yform"', null, static function () {
$packageNames = [];

foreach (Addon::getRegisteredAddons() as $package) {
if (!$package->isAvailable()) {
continue;
}

$packageNames[] = $package->name;
}

return $packageNames;
});
}

#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = $this->getStyle($input, $output);

$packageId = $input->getArgument('addon-id');

public function __invoke(
SymfonyStyle $io,
#[Argument('The name of the addon, e.g. "yform"', suggestedValues: static function (): array {
return array_keys(Addon::getAvailableAddons());
})] string $addon,
): int {
// the package manager don't know new packages in the addon folder
// so we need to make them available
AddonManager::synchronizeWithFileSystem();

$package = Addon::get($packageId);
$package = Addon::get($addon);
if (!$package) {
$io->error('Addon "' . $packageId . '" doesn\'t exists!');
$io->error('Addon "' . $addon . '" doesn\'t exists!');
return Command::FAILURE;
}

Expand Down
51 changes: 19 additions & 32 deletions src/Console/Command/AddonInstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,45 @@

namespace Redaxo\Core\Console\Command;

use Override;
use Redaxo\Core\Addon\Addon;
use Redaxo\Core\Addon\AddonManager;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
* @internal
*/
class AddonInstallCommand extends AbstractCommand
#[AsCommand(name: 'addon:install', description: 'Installs the selected addon')]
final class AddonInstallCommand extends AbstractCommand
{
#[Override]
protected function configure(): void
{
$this
->setDescription('Installs the selected addon')
->addArgument('addon-id', InputArgument::REQUIRED, 'The id of the addon, e.g. "yform"', null, static function () {
$packageNames = [];

foreach (Addon::getRegisteredAddons() as $package) {
// allow all packages, because we support --re-intall for already installed ones
$packageNames[] = $package->name;
}

return $packageNames;
})
->addOption('re-install', '-r', InputOption::VALUE_NONE, 'Allows to reinstall the addon without asking the User');
}

#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = $this->getStyle($input, $output);

$packageId = $input->getArgument('addon-id');

public function __invoke(
InputInterface $input,
OutputInterface $output,
SymfonyStyle $io,
#[Argument('The name of the addon, e.g. "yform"', suggestedValues: static function (): array {
// allow all packages, because we support --re-intall for already installed ones
return array_keys(Addon::getRegisteredAddons());
})] string $addon,
#[Option('Allows to reinstall the addon without asking the User', shortcut: 'r')] bool $reInstall = false,
): int {
// the package manager don't know new packages in the addon folder
// so we need to make them available
AddonManager::synchronizeWithFileSystem();

$package = Addon::get($packageId);
$package = Addon::get($addon);
if (!$package) {
$io->error('Addon "' . $packageId . '" doesn\'t exists!');
$io->error('Addon "' . $addon . '" doesn\'t exists!');
return Command::FAILURE;
}

if ($package->isInstalled() && !$input->getOption('re-install')) {
if ($package->isInstalled() && !$reInstall) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Addon "' . $package->name . '" is already installed. Should it be reinstalled? (y/n) ', false);
Expand Down
Loading