diff options
Diffstat (limited to 'core/Command/App')
-rw-r--r-- | core/Command/App/Disable.php | 83 | ||||
-rw-r--r-- | core/Command/App/Enable.php | 151 | ||||
-rw-r--r-- | core/Command/App/GetPath.php | 70 | ||||
-rw-r--r-- | core/Command/App/Install.php | 84 | ||||
-rw-r--r-- | core/Command/App/ListApps.php | 145 | ||||
-rw-r--r-- | core/Command/App/Remove.php | 124 | ||||
-rw-r--r-- | core/Command/App/Update.php | 119 |
7 files changed, 776 insertions, 0 deletions
diff --git a/core/Command/App/Disable.php b/core/Command/App/Disable.php new file mode 100644 index 00000000000..121ad3f010c --- /dev/null +++ b/core/Command/App/Disable.php @@ -0,0 +1,83 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Core\Command\App; + +use OCP\App\IAppManager; +use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface; +use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Disable extends Command implements CompletionAwareInterface { + protected int $exitCode = 0; + + public function __construct( + protected IAppManager $appManager, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('app:disable') + ->setDescription('disable an app') + ->addArgument( + 'app-id', + InputArgument::REQUIRED | InputArgument::IS_ARRAY, + 'disable the specified app' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $appIds = $input->getArgument('app-id'); + + foreach ($appIds as $appId) { + $this->disableApp($appId, $output); + } + + return $this->exitCode; + } + + private function disableApp(string $appId, OutputInterface $output): void { + if ($this->appManager->isEnabledForAnyone($appId) === false) { + $output->writeln('No such app enabled: ' . $appId); + return; + } + + try { + $this->appManager->disableApp($appId); + $appVersion = $this->appManager->getAppVersion($appId); + $output->writeln($appId . ' ' . $appVersion . ' disabled'); + } catch (\Exception $e) { + $output->writeln($e->getMessage()); + $this->exitCode = 2; + } + } + + /** + * @param string $optionName + * @param CompletionContext $context + * @return string[] + */ + public function completeOptionValues($optionName, CompletionContext $context): array { + return []; + } + + /** + * @param string $argumentName + * @param CompletionContext $context + * @return string[] + */ + public function completeArgumentValues($argumentName, CompletionContext $context): array { + if ($argumentName === 'app-id') { + return array_diff(\OC_App::getEnabledApps(true, true), $this->appManager->getAlwaysEnabledApps()); + } + return []; + } +} diff --git a/core/Command/App/Enable.php b/core/Command/App/Enable.php new file mode 100644 index 00000000000..3936acfbf6e --- /dev/null +++ b/core/Command/App/Enable.php @@ -0,0 +1,151 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Core\Command\App; + +use OC\Installer; +use OCP\App\AppPathNotFoundException; +use OCP\App\IAppManager; +use OCP\IGroup; +use OCP\IGroupManager; +use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface; +use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Enable extends Command implements CompletionAwareInterface { + protected int $exitCode = 0; + + public function __construct( + protected IAppManager $appManager, + protected IGroupManager $groupManager, + private Installer $installer, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('app:enable') + ->setDescription('enable an app') + ->addArgument( + 'app-id', + InputArgument::REQUIRED | InputArgument::IS_ARRAY, + 'enable the specified app' + ) + ->addOption( + 'groups', + 'g', + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'enable the app only for a list of groups' + ) + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'enable the app regardless of the Nextcloud version requirement' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $appIds = $input->getArgument('app-id'); + $groups = $this->resolveGroupIds($input->getOption('groups')); + $forceEnable = (bool)$input->getOption('force'); + + foreach ($appIds as $appId) { + $this->enableApp($appId, $groups, $forceEnable, $output); + } + + return $this->exitCode; + } + + /** + * @param string $appId + * @param array $groupIds + * @param bool $forceEnable + * @param OutputInterface $output + */ + private function enableApp(string $appId, array $groupIds, bool $forceEnable, OutputInterface $output): void { + $groupNames = array_map(function (IGroup $group) { + return $group->getDisplayName(); + }, $groupIds); + + if ($this->appManager->isEnabledForUser($appId) && $groupIds === []) { + $output->writeln($appId . ' already enabled'); + return; + } + + try { + if ($this->installer->isDownloaded($appId) === false) { + $this->installer->downloadApp($appId); + } + + $this->installer->installApp($appId, $forceEnable); + $appVersion = $this->appManager->getAppVersion($appId); + + if ($groupIds === []) { + $this->appManager->enableApp($appId, $forceEnable); + $output->writeln($appId . ' ' . $appVersion . ' enabled'); + } else { + $this->appManager->enableAppForGroups($appId, $groupIds, $forceEnable); + $output->writeln($appId . ' ' . $appVersion . ' enabled for groups: ' . implode(', ', $groupNames)); + } + } catch (AppPathNotFoundException $e) { + $output->writeln($appId . ' not found'); + $this->exitCode = 1; + } catch (\Exception $e) { + $output->writeln($e->getMessage()); + $this->exitCode = 1; + } + } + + /** + * @param array $groupIds + * @return array + */ + private function resolveGroupIds(array $groupIds): array { + $groups = []; + foreach ($groupIds as $groupId) { + $group = $this->groupManager->get($groupId); + if ($group instanceof IGroup) { + $groups[] = $group; + } + } + return $groups; + } + + /** + * @param string $optionName + * @param CompletionContext $context + * @return string[] + */ + public function completeOptionValues($optionName, CompletionContext $context): array { + if ($optionName === 'groups') { + return array_map(function (IGroup $group) { + return $group->getGID(); + }, $this->groupManager->search($context->getCurrentWord())); + } + return []; + } + + /** + * @param string $argumentName + * @param CompletionContext $context + * @return string[] + */ + public function completeArgumentValues($argumentName, CompletionContext $context): array { + if ($argumentName === 'app-id') { + $allApps = $this->appManager->getAllAppsInAppsFolders(); + return array_diff($allApps, \OC_App::getEnabledApps(true, true)); + } + return []; + } +} diff --git a/core/Command/App/GetPath.php b/core/Command/App/GetPath.php new file mode 100644 index 00000000000..3ba4ed7781b --- /dev/null +++ b/core/Command/App/GetPath.php @@ -0,0 +1,70 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Core\Command\App; + +use OC\Core\Command\Base; +use OCP\App\AppPathNotFoundException; +use OCP\App\IAppManager; +use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class GetPath extends Base { + public function __construct( + protected IAppManager $appManager, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + + $this + ->setName('app:getpath') + ->setDescription('Get an absolute path to the app directory') + ->addArgument( + 'app', + InputArgument::REQUIRED, + 'Name of the app' + ) + ; + } + + /** + * Executes the current command. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * @return int 0 if everything went fine, or an error code + */ + protected function execute(InputInterface $input, OutputInterface $output): int { + $appName = $input->getArgument('app'); + try { + $path = $this->appManager->getAppPath($appName); + } catch (AppPathNotFoundException) { + // App not found, exit with non-zero + return self::FAILURE; + } + $output->writeln($path); + return self::SUCCESS; + } + + /** + * @param string $argumentName + * @param CompletionContext $context + * @return string[] + */ + public function completeArgumentValues($argumentName, CompletionContext $context): array { + if ($argumentName === 'app') { + return $this->appManager->getAllAppsInAppsFolders(); + } + return []; + } +} diff --git a/core/Command/App/Install.php b/core/Command/App/Install.php new file mode 100644 index 00000000000..c8a396c8e36 --- /dev/null +++ b/core/Command/App/Install.php @@ -0,0 +1,84 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Core\Command\App; + +use OC\Installer; +use OCP\App\IAppManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Install extends Command { + public function __construct( + protected IAppManager $appManager, + private Installer $installer, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('app:install') + ->setDescription('install an app') + ->addArgument( + 'app-id', + InputArgument::REQUIRED, + 'install the specified app' + ) + ->addOption( + 'keep-disabled', + null, + InputOption::VALUE_NONE, + 'don\'t enable the app afterwards' + ) + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'install the app regardless of the Nextcloud version requirement' + ) + ->addOption( + 'allow-unstable', + null, + InputOption::VALUE_NONE, + 'allow installing an unstable releases' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $appId = $input->getArgument('app-id'); + $forceEnable = (bool)$input->getOption('force'); + + if ($this->appManager->isEnabledForAnyone($appId)) { + $output->writeln($appId . ' already installed'); + return 1; + } + + try { + $this->installer->downloadApp($appId, $input->getOption('allow-unstable')); + $result = $this->installer->installApp($appId, $forceEnable); + } catch (\Exception $e) { + $output->writeln('Error: ' . $e->getMessage()); + return 1; + } + + $appVersion = $this->appManager->getAppVersion($appId); + $output->writeln($appId . ' ' . $appVersion . ' installed'); + + if (!$input->getOption('keep-disabled')) { + $this->appManager->enableApp($appId); + $output->writeln($appId . ' enabled'); + } + + return 0; + } +} diff --git a/core/Command/App/ListApps.php b/core/Command/App/ListApps.php new file mode 100644 index 00000000000..dc947bea55f --- /dev/null +++ b/core/Command/App/ListApps.php @@ -0,0 +1,145 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OC\Core\Command\App; + +use OC\Core\Command\Base; +use OCP\App\IAppManager; +use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ListApps extends Base { + public function __construct( + protected IAppManager $appManager, + ) { + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + + $this + ->setName('app:list') + ->setDescription('List all available apps') + ->addOption( + 'shipped', + null, + InputOption::VALUE_REQUIRED, + 'true - limit to shipped apps only, false - limit to non-shipped apps only' + ) + ->addOption( + 'enabled', + null, + InputOption::VALUE_NONE, + 'shows only enabled apps' + ) + ->addOption( + 'disabled', + null, + InputOption::VALUE_NONE, + 'shows only disabled apps' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + if ($input->getOption('shipped') === 'true' || $input->getOption('shipped') === 'false') { + $shippedFilter = $input->getOption('shipped') === 'true'; + } else { + $shippedFilter = null; + } + + $showEnabledApps = $input->getOption('enabled') || !$input->getOption('disabled'); + $showDisabledApps = $input->getOption('disabled') || !$input->getOption('enabled'); + + $apps = $this->appManager->getAllAppsInAppsFolders(); + $enabledApps = $disabledApps = []; + $versions = $this->appManager->getAppInstalledVersions(); + + //sort enabled apps above disabled apps + foreach ($apps as $app) { + if ($shippedFilter !== null && $this->appManager->isShipped($app) !== $shippedFilter) { + continue; + } + if ($this->appManager->isEnabledForAnyone($app)) { + $enabledApps[] = $app; + } else { + $disabledApps[] = $app; + } + } + + $apps = []; + + if ($showEnabledApps) { + $apps['enabled'] = []; + + sort($enabledApps); + foreach ($enabledApps as $app) { + $apps['enabled'][$app] = $versions[$app] ?? true; + } + } + + if ($showDisabledApps) { + $apps['disabled'] = []; + + sort($disabledApps); + foreach ($disabledApps as $app) { + $apps['disabled'][$app] = $this->appManager->getAppVersion($app) . (isset($versions[$app]) ? ' (installed ' . $versions[$app] . ')' : ''); + } + } + + $this->writeAppList($input, $output, $apps); + return 0; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @param array $items + */ + protected function writeAppList(InputInterface $input, OutputInterface $output, $items): void { + switch ($input->getOption('output')) { + case self::OUTPUT_FORMAT_PLAIN: + if (isset($items['enabled'])) { + $output->writeln('Enabled:'); + parent::writeArrayInOutputFormat($input, $output, $items['enabled']); + } + + if (isset($items['disabled'])) { + $output->writeln('Disabled:'); + parent::writeArrayInOutputFormat($input, $output, $items['disabled']); + } + break; + + default: + parent::writeArrayInOutputFormat($input, $output, $items); + break; + } + } + + /** + * @param string $optionName + * @param CompletionContext $context + * @return array + */ + public function completeOptionValues($optionName, CompletionContext $context): array { + if ($optionName === 'shipped') { + return ['true', 'false']; + } + return []; + } + + /** + * @param string $argumentName + * @param CompletionContext $context + * @return string[] + */ + public function completeArgumentValues($argumentName, CompletionContext $context): array { + return []; + } +} diff --git a/core/Command/App/Remove.php b/core/Command/App/Remove.php new file mode 100644 index 00000000000..d43bfa96ccc --- /dev/null +++ b/core/Command/App/Remove.php @@ -0,0 +1,124 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Core\Command\App; + +use OC\Installer; +use OCP\App\IAppManager; +use Psr\Log\LoggerInterface; +use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface; +use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; +use Symfony\Component\Console\Command\Command; +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 Throwable; + +class Remove extends Command implements CompletionAwareInterface { + public function __construct( + protected IAppManager $manager, + private Installer $installer, + private LoggerInterface $logger, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('app:remove') + ->setDescription('remove an app') + ->addArgument( + 'app-id', + InputArgument::REQUIRED, + 'remove the specified app' + ) + ->addOption( + 'keep-data', + null, + InputOption::VALUE_NONE, + 'keep app data and do not remove them' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $appId = $input->getArgument('app-id'); + + // Check if the app is enabled + if (!$this->manager->isEnabledForAnyone($appId)) { + $output->writeln($appId . ' is not enabled'); + return 1; + } + + // Removing shipped apps is not possible, therefore we pre-check that + // before trying to remove it + if ($this->manager->isShipped($appId)) { + $output->writeln($appId . ' could not be removed as it is a shipped app'); + return 1; + } + + // If we want to keep the data of the app, we simply don't disable it here. + // App uninstall tasks are being executed when disabled. More info: PR #11627. + if (!$input->getOption('keep-data')) { + try { + $this->manager->disableApp($appId); + $output->writeln($appId . ' disabled'); + } catch (Throwable $e) { + $output->writeln('<error>Error: ' . $e->getMessage() . '</error>'); + $this->logger->error($e->getMessage(), [ + 'app' => 'CLI', + 'exception' => $e, + ]); + return 1; + } + } + + // Let's try to remove the app... + try { + $result = $this->installer->removeApp($appId); + } catch (Throwable $e) { + $output->writeln('<error>Error: ' . $e->getMessage() . '</error>'); + $this->logger->error($e->getMessage(), [ + 'app' => 'CLI', + 'exception' => $e, + ]); + return 1; + } + + if ($result === false) { + $output->writeln($appId . ' could not be removed'); + return 1; + } + + $appVersion = $this->manager->getAppVersion($appId); + $output->writeln($appId . ' ' . $appVersion . ' removed'); + + return 0; + } + + /** + * @param string $optionName + * @param CompletionContext $context + * @return string[] + */ + public function completeOptionValues($optionName, CompletionContext $context): array { + return []; + } + + /** + * @param string $argumentName + * @param CompletionContext $context + * @return string[] + */ + public function completeArgumentValues($argumentName, CompletionContext $context): array { + if ($argumentName === 'app-id') { + return $this->manager->getEnabledApps(); + } + return []; + } +} diff --git a/core/Command/App/Update.php b/core/Command/App/Update.php new file mode 100644 index 00000000000..71c7f84e5b0 --- /dev/null +++ b/core/Command/App/Update.php @@ -0,0 +1,119 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Core\Command\App; + +use OC\Installer; +use OCP\App\AppPathNotFoundException; +use OCP\App\IAppManager; +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class Update extends Command { + public function __construct( + protected IAppManager $manager, + private Installer $installer, + private LoggerInterface $logger, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('app:update') + ->setDescription('update an app or all apps') + ->addArgument( + 'app-id', + InputArgument::OPTIONAL, + 'update the specified app' + ) + ->addOption( + 'all', + null, + InputOption::VALUE_NONE, + 'update all updatable apps' + ) + ->addOption( + 'showonly', + null, + InputOption::VALUE_NONE, + 'show update(s) without updating' + ) + ->addOption( + 'allow-unstable', + null, + InputOption::VALUE_NONE, + 'allow updating to unstable releases' + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $singleAppId = $input->getArgument('app-id'); + $updateFound = false; + + if ($singleAppId) { + $apps = [$singleAppId]; + try { + $this->manager->getAppPath($singleAppId); + } catch (AppPathNotFoundException $e) { + $output->writeln($singleAppId . ' not installed'); + return 1; + } + } elseif ($input->getOption('all') || $input->getOption('showonly')) { + $apps = $this->manager->getAllAppsInAppsFolders(); + } else { + $output->writeln('<error>Please specify an app to update or "--all" to update all updatable apps"</error>'); + return 1; + } + + $return = 0; + foreach ($apps as $appId) { + $newVersion = $this->installer->isUpdateAvailable($appId, $input->getOption('allow-unstable')); + if ($newVersion) { + $updateFound = true; + $output->writeln($appId . ' new version available: ' . $newVersion); + + if (!$input->getOption('showonly')) { + try { + $result = $this->installer->updateAppstoreApp($appId, $input->getOption('allow-unstable')); + } catch (\Exception $e) { + $this->logger->error('Failure during update of app "' . $appId . '"', [ + 'app' => 'app:update', + 'exception' => $e, + ]); + $output->writeln('Error: ' . $e->getMessage()); + $result = false; + $return = 1; + } + + if ($result === false) { + $output->writeln($appId . ' couldn\'t be updated'); + $return = 1; + } else { + $output->writeln($appId . ' updated'); + } + } + } + } + + if (!$updateFound) { + if ($singleAppId) { + $output->writeln($singleAppId . ' is up-to-date or no updates could be found'); + } else { + $output->writeln('All apps are up-to-date or no updates could be found'); + } + } + + return $return; + } +} |